JavaRush /Java блогы /Random-KK /equals & hashCode әдістері: пайдалану тәжірибесі

equals & hashCode әдістері: пайдалану тәжірибесі

Топта жарияланған
Сәлеметсіз бе! Бүгін біз Java тіліндегі екі маңызды әдіс туралы сөйлесетін боламыз - equals()және hashCode(). Бұл біз олармен бірінші рет кездесіп отырған жоқпыз: JavaRush курсының басында бұл туралы қысқаша лекцияequals() болды – ұмытып қалсаңыз немесе бұрын көрмесеңіз оқыңыз. Әдістері &  hashCode: пайдалану тәжірибесі - 1Бүгінгі сабақта біз осы ұғымдар туралы егжей-тегжейлі сөйлесетін боламыз - маған сеніңіз, сөйлесетін көп нәрсе бар! Жаңа нәрсеге көшпес бұрын, бұрын өткен нәрселер туралы жадымызды жаңартып алайық :) Естеріңізде болса, екі нысанды « ==» операторы арқылы әдеттегі салыстыру жаман идея, өйткені « ==» сілтемелерді салыстырады. Жақында өткен лекциядағы көліктерге мысал:
public class Car {

   String model;
   int maxSpeed;

   public static void main(String[] args) {

       Car car1 = new Car();
       car1.model = "Ferrari";
       car1.maxSpeed = 300;

       Car car2 = new Car();
       car2.model = "Ferrari";
       car2.maxSpeed = 300;

       System.out.println(car1 == car2);
   }
}
Консоль шығысы:

false
Біз класстың екі бірдей нысанын жасаған сияқтымыз Car: екі машинадағы барлық өрістер бірдей, бірақ салыстыру нәтижесі әлі де жалған. Себебін біз қазірдің өзінде білеміз: сілтемелер car1мен car2жадтағы әртүрлі мекенжайларды көрсетеді, сондықтан олар тең емес. Біз әлі де екі сілтемені емес, екі нысанды салыстырғымыз келеді. Объектілерді салыстырудың ең жақсы шешімі - equals().

equals() әдісі

Естеріңізде болса, біз бұл әдісті нөлден жасамаймыз, бірақ оны жоққа шығарамыз - бұл әдіс equals()сыныпта анықталған Object. Дегенмен, оның әдеттегі түрінде оның пайдасы аз:
public boolean equals(Object obj) {
   return (this == obj);
}
equals()Сыныпта әдіс осылай анықталады Object. Сілтемелерді бірдей салыстыру. Ол неге бұлай жаратылды? Тілді жасаушылар сіздің бағдарламаңыздағы қандай нысандар тең, қайсысы тең емес екенін қайдан біледі? :) Бұл әдістің негізгі идеясы equals()- сыныпты жасаушының өзі осы класс an objectілерінің теңдігі тексерілетін сипаттамаларды анықтайды. equals()Бұл әрекетті орындау арқылы сіз сыныптағы әдісті қайта анықтайсыз . Егер сіз «сипаттамаларды өзіңіз анықтайсыз» дегеннің мағынасын толық түсінбесеңіз, мысалды қарастырайық. Міне, адамның қарапайым класы - Man.
public class Man {

   private String noseSize;
   private String eyesColor;
   private String haircut;
   private boolean scars;
   private int dnaCode;

public Man(String noseSize, String eyesColor, String haircut, boolean scars, int dnaCode) {
   this.noseSize = noseSize;
   this.eyesColor = eyesColor;
   this.haircut = haircut;
   this.scars = scars;
   this.dnaCode = dnaCode;
}

   //getters, setters, etc.
}
Екі адамның егіз туыстарын немесе жай ғана қос туыстарын анықтау үшін бағдарлама жазып жатырмыз делік. Бізде бес сипаттама бар: мұрын өлшемі, көздің түсі, шаш үлгісі, тыртықтардың болуы және ДНҚ биологиялық сынамасының нәтижелері (қарапайымдылық үшін - codeтық нөмір түрінде). Осы сипаттамалардың қайсысы біздің бағдарламамыз егіз туыстарды анықтауға мүмкіндік береді деп ойлайсыз? Әдістері &  hashCode: қолдану тәжірибесі - 2Әрине, тек биологиялық сынақ кепілдік бере алады. Екі адамның көзінің түсі, шаш үлгісі, мұрны, тіпті тыртықтары бірдей болуы мүмкін - әлемде көптеген адамдар бар және кездейсоқтықтан аулақ болу мүмкін емес. Бізге сенімді механизм қажет: тек ДНҚ тестінің нәтижесі дәл қорытынды жасауға мүмкіндік береді. Бұл біздің әдісіміз үшін нені білдіреді equals()? ManБіз оны бағдарламамыздың талаптарын ескере отырып, сыныпта қайта анықтауымыз керек . Әдіс екі нысанның өрісін салыстыруы керек int dnaCode, ал егер олар тең болса, онда нысандар тең болады.
@Override
public boolean equals(Object o) {
   Man man = (Man) o;
   return dnaCode == man.dnaCode;
}
Бұл шынымен де қарапайым ма? Онша емес. Біз бір нәрсені сағындық. Бұл жағдайда біздің an objectілеріміз үшін олардың теңдігі белгіленетін бір ғана «маңызды» өрісті анықтадық - dnaCode. Енді бізде 1 емес, 50 ​​осындай «маңызды» өрістер болатынын елестетіп көріңіз.Ал егер екі нысанның барлық 50 өрісі тең болса, онда нысандар тең болады. Бұл да болуы мүмкін. Негізгі мәселе, 50 өрістің теңдігін есептеу көп уақытты және ресурстарды қажет ететін процесс. Енді сыныпқа қосымша бізде өрістерімен бірдей Manсынып бар екенін елестетіп көріңіз . Егер басқа бағдарламашы сіздің сабақтарыңызды пайдаланса, ол өз бағдарламасында келесідей нәрсені оңай жаза алады: WomanMan
public static void main(String[] args) {

   Man man = new Man(........); //a bunch of parameters in the constructor

   Woman woman = new Woman(.........);//same bunch of parameters.

   System.out.println(man.equals(woman));
}
Бұл жағдайда өріс мәндерін тексерудің қажеті жоқ: біз екі түрлі класстың an objectілерін қарап жатқанымызды көреміз және олар принцип бойынша тең бола алмайды! equals()Бұл екі бірдей сыныптың an objectілерін салыстыру әдісіне чек қою керек екенін білдіреді . Бұл туралы ойлағанымыз жақсы!
@Override
public boolean equals(Object o) {
   if (getClass() != o.getClass()) return false;
   Man man = (Man) o;
   return dnaCode == man.dnaCode;
}
Бірақ біз басқа нәрсені ұмытып кеткен шығармыз? Хм... Ең болмағанда, біз an objectіні өзімен салыстырмайтынымызды тексеруіміз керек! Егер A және B сілтемелері жадтағы бір мекенжайды көрсетсе, онда олар бірдей нысан болып табылады және бізге 50 өрісті салыстыру үшін уақытты жоғалтудың қажеті жоқ.
@Override
public boolean equals(Object o) {
   if (this == o) return true;
   if (getClass() != o.getClass()) return false;
   Man man = (Man) o;
   return dnaCode == man.dnaCode;
}
Бұған қоса, үшін чекті қосу зиян тигізбейді null: ешбір нысан -ге тең бола алмайды null, бұл жағдайда қосымша тексерулердің мәні жоқ. Осының барлығын ескере отырып, біздің equals()сынып әдісі Manкелесідей болады:
@Override
public boolean equals(Object o) {
   if (this == o) return true;
   if (o == null || getClass() != o.getClass()) return false;
   Man man = (Man) o;
   return dnaCode == man.dnaCode;
}
Біз жоғарыда аталған барлық бастапқы тексерулерді орындаймыз. Егер бұл анықталса:
  • біз бір класстың екі an objectісін салыстырамыз
  • бұл бірдей нысан емес
  • біз an objectімізді салыстырмаймызnull
...содан кейін маңызды сипаттамаларды салыстыруға көшеміз. Біздің жағдайда dnaCodeекі нысанның өрістері. Әдісті қайта анықтау кезінде equals()мына талаптарды орындаңыз:
  1. Рефлексивтілік.

    equals()Кез келген нысан өзі үшін болуы керек .
    Бұл талапты біз қазірдің өзінде ескердік. Біздің әдісімізде:

    if (this == o) return true;

  2. Симметрия.

    Егер болса a.equals(b) == true, онда b.equals(a)ол қайтарылуы керек true.
    Біздің әдіс те осы талапқа сай.

  3. Өтпелілік.

    Егер екі an object қандай да бір үшінші нысанға тең болса, онда олар бір-біріне тең болуы керек.
    Егер a.equals(b) == trueжәне болса a.equals(c) == true, онда тексеру b.equals(c)ақиқат мәнін қайтаруы керек.

  4. Тұрақтылық.

    Жұмыстың нәтижелері equals()оған енгізілген өрістер өзгерген кезде ғана өзгеруі керек. Егер екі an objectінің деректері өзгермеген болса, тексеру нәтижелері equals()әрқашан бірдей болуы керек.

  5. -мен теңсіздік null.

    Кез келген нысан үшін чек a.equals(null)жалған мәнін қайтаруы керек.Бұл
    жай ғана кейбір «пайдалы ұсыныстар» жиынтығы емес, Oracle құжаттамасында жазылған әдістердің қатаң шарты.

hashCode() әдісі

Енді әдіс туралы сөйлесейік hashCode(). Ол не үшін қажет? Дәл сол мақсат үшін - an objectілерді салыстыру. Бірақ бізде ол қазірдің өзінде бар equals()! Неліктен басқа әдіс? Жауап қарапайым: өнімділікті арттыру. Java тілінде , әдісі арқылы ұсынылған хэш функциясы hashCode()кез келген нысан үшін тұрақты ұзындықты сандық мәнді қайтарады. Java жағдайында әдіс hashCode()32 бит түрінің санын қайтарады int. Екі санды бір-бірімен салыстыру әдісі арқылы екі нысанды салыстырудан әлдеқайда жылдамырақ equals(), әсіресе ол көптеген өрістерді пайдаланса. Егер біздің бағдарламамыз an objectілерді салыстыратын болса, мұны хэш-code арқылы жасау әлдеқайда оңай, егер олар тең болса ғана hashCode()- арқылы салыстыруға өтіңіз equals(). Айтпақшы, хэш негізіндегі деректер құрылымдары қалай жұмыс істейді, мысалы, сіз білетін құрылым HashMap! Әдіс hashCode(), дәл солай equals(), әзірлеушінің өзі басқарады. Сондай-ақ equals(), сияқты әдісте hashCode()Oracle құжаттамасында көрсетілген ресми талаптар бар:
  1. Екі нысан тең болса (яғни әдіс equals()шын мәнін қайтарады), оларда бірдей хэш codeы болуы керек.

    Әйтпесе, біздің әдістеріміз мағынасыз болады. Жоғарыда hashCode()айтқанымыздай, өнімділікті жақсарту үшін бірінші орында болуы керек. Егер хэш codeтары әртүрлі болса, нысандар шын мәнінде тең болса да, тексеру жалған мәнін қайтарады (әдісте анықталғандай equals()).

  2. Егер әдіс hashCode()бір нысанда бірнеше рет шақырылса, ол әр кезде бірдей нөмірді қайтаруы керек.

  3. 1-ереже керісінше жұмыс істемейді. Екі түрлі нысанда бірдей хэш-code болуы мүмкін.

Үшінші ереже аздап шатастырады. Бұл қалай болуы мүмкін? Түсіндіру өте қарапайым. Әдіс hashCode()қайтарады int. int32 биттік сан болып табылады. Оның мәндерінің шектеулі саны бар - -2 147 483 648-ден +2 147 483 647-ге дейін. Басқаша айтқанда, санның 4 миллиардтан астам вариациясы бар int. Енді сіз Жердегі барлық тірі адамдар туралы деректерді сақтауға арналған бағдарлама жасап жатырсыз деп елестетіңіз. Әр адамның өз сынып an objectісі болады Man. Жер бетінде ~7,5 миллиард адам тұрады. Басқаша айтқанда, an objectілерді сандарға түрлендіру үшін қаншалықты жақсы алгоритм Manжазсақ та, бізде сандар жеткіліксіз. Бізде бар болғаны 4,5 миллиард опция және одан да көп адамдар бар. Бұл қанша тырысқанымызға қарамастан, хэш-codeтар әртүрлі адамдар үшін бірдей болады дегенді білдіреді. Бұл жағдай (екі түрлі нысанның хэш-codeтары сәйкес келеді) соқтығыс деп аталады. Әдісті қайта анықтау кезіндегі бағдарламашы мақсаттарының бірі hashCode()соқтығысудың ықтимал санын мүмкіндігінше азайту болып табылады. Осы ережелердің барлығын ескере отырып, біздің hashCode()сыныпқа арналған әдісіміз қандай болады ? ManБұл сияқты:
@Override
public int hashCode() {
   return dnaCode;
}
Таң қалдыңыз ба? :) Күтпеген жерден, бірақ талаптарға қарасаңыз, біз бәрін орындайтынымызды көресіз. Біздікі equals()шын мәнін қайтаратын нысандар ішінде тең болады hashCode(). Егер біздің екі нысанның Manмәні бірдей болса equals(яғни олардың мәні бірдей болса dnaCode), біздің әдіс бірдей санды қайтарады. Күрделі мысалды қарастырайық. Біздің бағдарлама коллектор клиенттері үшін сәнді автокөліктерді таңдауы керек делік. Жинау күрделі нәрсе, оның көптеген ерекшеліктері бар. 1963 жылғы көлік 1964 жылғы көліктен 100 есе қымбат болуы мүмкін. 1970 жылғы қызыл көлік сол жылғы сол маркадағы көк көліктен 100 есе қымбат болуы мүмкін. Әдістері &  hashCode: қолдану тәжірибесі - 4Бірінші жағдайда, сыныппен Manбіз өрістердің көпшілігін (яғни, адам сипаттамалары) елеусіз деп алып тастадық және салыстыру үшін өрісті ғана пайдаландық dnaCode. Мұнда біз өте ерекше сферамен жұмыс істейміз және елеусіз бөлшектер болуы мүмкін емес! Міне, біздің сынып LuxuryAuto:
public class LuxuryAuto {

   private String model;
   private int manufactureYear;
   private int dollarPrice;

   public LuxuryAuto(String model, int manufactureYear, int dollarPrice) {
       this.model = model;
       this.manufactureYear = manufactureYear;
       this.dollarPrice = dollarPrice;
   }

   //... getters, setters, etc.
}
Бұл жерде салыстыру кезінде біз барлық өрістерді ескеруіміз керек. Кез келген қате клиент үшін жүздеген мың доллар шығынға ұшырауы мүмкін, сондықтан қауіпсіз болған дұрыс:
@Override
public boolean equals(Object o) {
   if (this == o) return true;
   if (o == null || getClass() != o.getClass()) return false;

   LuxuryAuto that = (LuxuryAuto) o;

   if (manufactureYear != that.manufactureYear) return false;
   if (dollarPrice != that.dollarPrice) return false;
   return model.equals(that.model);
}
Біздің әдісімізде equals()біз бұрын айтқан барлық тексерулерді ұмытпадық. Бірақ қазір біз an objectілеріміздің үш өрісінің әрқайсысын салыстырамыз. Бұл бағдарламада теңдік барлық салада абсолютті болуы керек. Ал ше hashCode?
@Override
public int hashCode() {
   int result = model == null ? 0 : model.hashCode();
   result = result + manufactureYear;
   result = result + dollarPrice;
   return result;
}
modelБіздің сыныптағы өріс - бұл жол. Бұл ыңғайлы: Stringәдіс hashCode()сыныпта бұрыннан жойылған. Біз өрістің хэш codeын есептейміз modelжәне оған қалған екі сандық өрістің қосындысын қосамыз. Java тілінде соқтығыстардың санын азайту үшін қолданылатын кішкене трюк бар: хэш-codeты есептеген кезде аралық нәтижені тақ жай санға көбейтіңіз. Ең жиі қолданылатын сан – 29 немесе 31. Біз қазір математиканың егжей-тегжейіне тоқталмаймыз, бірақ болашақта анықтама үшін аралық нәтижелерді жеткілікті үлкен тақ санға көбейту хэш нәтижелерін «таратуға» көмектесетінін есте сақтаңыз. функциясын орындайды және бірдей хэшcodeы бар азырақ нысандармен аяқталады. LuxuryAuto-дағы әдісіміз үшін hashCode()ол келесідей болады:
@Override
public int hashCode() {
   int result = model == null ? 0 : model.hashCode();
   result = 31 * result + manufactureYear;
   result = 31 * result + dollarPrice;
   return result;
}
Бұл механизмнің барлық қыр-сырлары туралы толығырақ StackOverflow сайтындағы осы посттан , сондай-ақ Джошуа Блохтың « Тиімді Java » кітабынан оқи аласыз . Соңында айта кететін тағы бір маңызды жайт бар. Әр жолы қайта анықтау кезінде equals()біз hashCode()осы әдістерде ескерілген нысанның белгілі өрістерін таңдадық. equals()Бірақ біз әр түрлі өрістерді есепке ала аламыз ба hashCode()? Техникалық тұрғыдан біз аламыз. Бірақ бұл жаман идея, сондықтан мынау:
@Override
public boolean equals(Object o) {
   if (this == o) return true;
   if (o == null || getClass() != o.getClass()) return false;

   LuxuryAuto that = (LuxuryAuto) o;

   if (manufactureYear != that.manufactureYear) return false;
   return dollarPrice == that.dollarPrice;
}

@Override
public int hashCode() {
   int result = model == null ? 0 : model.hashCode();
   result = 31 * result + manufactureYear;
   result = 31 * result + dollarPrice;
   return result;
}
Міне, LuxuryAuto сыныбына equals()арналған әдістеріміз . hashCode()Әдіс hashCode()өзгеріссіз қалды және equals()біз өрісті әдістен алып тастадық model. Енді модель екі нысанды арқылы салыстыруға арналған сипаттама емес equals(). Бірақ хэш-codeты есептеу кезінде бәрібір ескеріледі. Нәтижесінде біз не аламыз? Екі көлік жасап, оны тексерейік!
public class Main {

   public static void main(String[] args) {

       LuxuryAuto ferrariGTO = new LuxuryAuto("Ferrari 250 GTO", 1963, 70000000);
       LuxuryAuto ferrariSpider = new LuxuryAuto("Ferrari 335 S Spider Scaglietti", 1963, 70000000);

       System.out.println("Are these two objects equal to each other?");
       System.out.println(ferrariGTO.equals(ferrariSpider));

       System.out.println("What are their hash codes?");
       System.out.println(ferrariGTO.hashCode());
       System.out.println(ferrariSpider.hashCode());
   }
}

Эти два an object равны друг другу?
true
Какие у них хэш-codeы?
-1372326051
1668702472
Қате! Әртүрлі өрістерді пайдалану арқылы equals()біз hashCode()олар үшін жасалған келісімшартты бұздық! Екі бірдей equals()нысанның хэш-codeы бірдей болуы керек. Біз оларға әртүрлі мағына бердік. Мұндай қателер ең керемет салдарға әкелуі мүмкін, әсіресе хэштерді пайдаланатын жинақтармен жұмыс істегенде. Сондықтан, қайта анықтау кезінде equals()және hashCode()бірдей өрістерді пайдалану дұрыс болады. Дәріс өте ұзақ болды, бірақ бүгін сіз көптеген жаңа нәрселерді білдіңіз! :) Мәселелерді шешуге қайта оралу уақыты келді!
Пікірлер
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION