JavaRush /Java blogi /Random-UZ /teng va hashCode usullari: foydalanish amaliyoti

teng va hashCode usullari: foydalanish amaliyoti

Guruhda nashr etilgan
Salom! Bugun biz Java-da ikkita muhim usul haqida gaplashamiz - equals()va hashCode(). Biz ular bilan birinchi marta uchrashayotganimiz yo‘q: JavaRush kursining boshida bu haqda qisqacha ma’ruzaequals() bor edi – agar uni unutgan bo‘lsangiz yoki avval ko‘rmagan bo‘lsangiz, o‘qing. Usullari teng & amp;  hashCode: foydalanish amaliyoti - 1Bugungi darsda biz ushbu tushunchalar haqida batafsil gaplashamiz - menga ishoning, gaplashadigan ko'p narsa bor! Va yangi narsaga o'tishdan oldin, biz allaqachon yoritgan narsalar bo'yicha xotiramizni yangilaymiz :) Yodingizda bo'lsa, " ==" operatori yordamida ikkita ob'ektni odatiy taqqoslash yomon fikrdir, chunki " ==" havolalarni taqqoslaydi. Yaqinda o'tkazilgan ma'ruzadagi mashinalar misolimiz:
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);
   }
}
Konsol chiqishi:

false
Biz sinfning ikkita bir xil ob'ektini yaratganga o'xshaymiz Car: ikkita mashinadagi barcha maydonlar bir xil, ammo taqqoslash natijasi hali ham noto'g'ri. Biz allaqachon sababini bilamiz: havolalar car1va car2xotiradagi turli manzillarga ishora qiladi, shuning uchun ular teng emas. Biz hali ham ikkita havolani emas, ikkita ob'ektni solishtirmoqchimiz. Ob'ektlarni solishtirish uchun eng yaxshi yechim equals().

teng() usuli

Esingizda bo'lsa, biz bu usulni noldan yaratmaymiz, lekin uni bekor qilamiz - axir, usul equals()sinfda aniqlanadi Object. Biroq, odatdagi shaklda u juda kam foyda keltiradi:
public boolean equals(Object obj) {
   return (this == obj);
}
equals()Usul sinfda shunday aniqlanadi Object. Havolalarni bir xil taqqoslash. Nega u shunday yaratilgan? Xo'sh, tilni yaratuvchilar sizning dasturingizdagi qaysi ob'ektlar teng va qaysi biri teng emasligini qayerdan bilishadi? :) Bu usulning asosiy g'oyasi equals()- sinf yaratuvchisi ushbu sinf ob'ektlarining tengligi tekshiriladigan xususiyatlarni o'zi belgilaydi. equals()Buni qilish orqali siz sinfingizdagi usulni bekor qilasiz . Agar siz "xususiyatlarni o'zingiz belgilaysiz" degan ma'noni to'liq tushunmasangiz, keling, misolni ko'rib chiqaylik. Bu erda oddiy odamlar sinfi - 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.
}
Aytaylik, biz ikki kishining egizaklar yoki shunchaki doppelgängerlar tomonidan qarindoshligini aniqlashimiz kerak bo'lgan dastur yozyapmiz. Bizda beshta xususiyat mavjud: burun kattaligi, ko'z rangi, soch turmagi, chandiqlar mavjudligi va DNK biologik testi natijalari (oddiylik uchun - kod raqami shaklida). Sizningcha, ushbu xususiyatlardan qaysi biri bizning dasturimizga egizak qarindoshlarni aniqlash imkonini beradi? Usullari teng & amp;  hashCode: foydalanish amaliyoti - 2Albatta, faqat biologik test kafolat berishi mumkin. Ikki kishining ko‘z rangi, soch turmagi, burni, hatto chandiqlari ham bir xil bo‘lishi mumkin – dunyoda juda ko‘p odamlar bor va tasodiflardan qochishning iloji yo‘q. Bizga ishonchli mexanizm kerak: faqat DNK testining natijasi aniq xulosa chiqarishga imkon beradi. Bu bizning usulimiz uchun nimani anglatadi equals()? ManBiz uni dasturimiz talablarini hisobga olgan holda sinfda qayta belgilashimiz kerak . Usul ikkita ob'ektning maydonini solishtirishi kerak int dnaCodeva agar ular teng bo'lsa, u holda ob'ektlar tengdir.
@Override
public boolean equals(Object o) {
   Man man = (Man) o;
   return dnaCode == man.dnaCode;
}
Bu haqiqatan ham shunchalik oddiymi? Unchalik emas. Biz bir narsani o'tkazib yubordik. Bunday holda, bizning ob'ektlarimiz uchun ularning tengligi o'rnatiladigan faqat bitta "muhim" maydonni aniqladik - dnaCode. Endi tasavvur qiling-a, bizda 1 ta emas, balki 50 ta shunday "muhim" maydonlar bo'ladi. Va agar ikkita ob'ektning barcha 50 ta maydoni teng bo'lsa, u holda ob'ektlar teng bo'ladi. Bu ham sodir bo'lishi mumkin. Asosiy muammo shundaki, 50 ta maydonning tengligini hisoblash ko'p vaqt va resurslarni talab qiluvchi jarayondir. Endi tasavvur qiling-a, sinfga qo'shimcha ravishda bizda bilan bir xil maydonlarga Manega sinf mavjud . Va agar boshqa dasturchi sizning darslaringizdan foydalansa, u o'z dasturiga osongina yozishi mumkin: 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));
}
Bunday holda, maydon qiymatlarini tekshirishning ma'nosi yo'q: biz ikki xil sinf ob'ektlarini ko'rib chiqayotganimizni ko'ramiz va ular printsipial jihatdan teng bo'lishi mumkin emas! equals()Bu shuni anglatadiki, biz ikkita bir xil sinf ob'ektlarini taqqoslash usuliga chek qo'yishimiz kerak . Bu haqda o'ylaganimiz yaxshi!
@Override
public boolean equals(Object o) {
   if (getClass() != o.getClass()) return false;
   Man man = (Man) o;
   return dnaCode == man.dnaCode;
}
Ammo, ehtimol, biz boshqa narsani unutganmizmi? Hmm ... Hech bo'lmaganda, biz ob'ektni o'zi bilan taqqoslamayotganimizni tekshirishimiz kerak! Agar A va B havolalari xotiradagi bir xil manzilga ishora qilsa, ular bir xil ob'ektdir va biz 50 ta maydonni solishtirishga vaqt sarflashimiz shart emas.
@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;
}
Bunga qo'shimcha ravishda, uchun chek qo'shish zarar qilmaydi null: hech qanday ob'ekt ga teng bo'lishi mumkin emas null, bu holda qo'shimcha tekshiruvlarda hech qanday nuqta yo'q. Bularning barchasini inobatga olgan holda, bizning equals()sinf uslubimiz Manquyidagicha ko'rinadi:
@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;
}
Biz yuqorida aytib o'tilgan barcha dastlabki tekshiruvlarni amalga oshiramiz. Agar shunday bo'lsa:
  • biz bir xil sinfning ikkita ob'ektini solishtiramiz
  • bu bir xil ob'ekt emas
  • biz ob'ektimizni bilan solishtirmayapmiznull
...keyin biz muhim xususiyatlarni solishtirishga o'tamiz. Bizning holatda, dnaCodeikkita ob'ektning maydonlari. Usulni bekor qilishda equals()quyidagi talablarga rioya qiling:
  1. Reflektorlik.

    Har qanday ob'ekt o'zi uchun bo'lishi kerak equals().
    Biz bu talabni allaqachon inobatga olganmiz. Bizning usulimizda aytiladi:

    if (this == o) return true;

  2. Simmetriya.

    Agar a.equals(b) == true, keyin b.equals(a)qaytib kelishi kerak true.
    Bizning uslubimiz ham bu talabga javob beradi.

  3. Tranzitivlik.

    Agar ikkita ob'ekt uchinchi ob'ektga teng bo'lsa, ular bir-biriga teng bo'lishi kerak.
    Agar a.equals(b) == trueva a.equals(c) == truebo'lsa, chek b.equals(c)ham rost bo'lishi kerak.

  4. Doimiylik.

    Ish natijalari equals()faqat unga kiritilgan maydonlar o'zgarganda o'zgarishi kerak. Agar ikkita ob'ektning ma'lumotlari o'zgarmagan bo'lsa, tekshirish natijalari equals()har doim bir xil bo'lishi kerak.

  5. bilan tengsizlik null.

    Har qanday ob'ekt uchun chek a.equals(null)noto'g'ri bo'lishi kerak,
    bu shunchaki ba'zi "foydali tavsiyalar" to'plami emas, balki Oracle hujjatlarida belgilangan usullarning qat'iy shartnomasidir.

hashCode() usuli

Endi usul haqida gapiraylik hashCode(). Nima uchun kerak? Aynan shu maqsadda - ob'ektlarni taqqoslash. Ammo bizda allaqachon bor equals()! Nega boshqa usul? Javob oddiy: samaradorlikni oshirish. Java'da , usuli bilan ifodalanadigan xesh funksiyasi hashCode()har qanday ob'ekt uchun belgilangan uzunlikdagi raqamli qiymatni qaytaradi. Java holatida usul hashCode()32 bitli turdagi sonni qaytaradi int. Ikkita raqamni bir-biri bilan taqqoslash usuli yordamida ikkita ob'ektni taqqoslashdan ko'ra tezroq equals(), ayniqsa u ko'p maydonlardan foydalansa. Agar bizning dasturimiz ob'ektlarni solishtiradigan bo'lsa, buni xesh-kod orqali qilish ancha oson bo'ladi va faqat ular teng bo'lsa hashCode()- taqqoslash orqali davom eting equals(). Aytgancha, xeshga asoslangan ma'lumotlar tuzilmalari qanday ishlaydi - masalan, siz bilganingiz HashMap! Usul hashCode(), xuddi , kabi equals(), ishlab chiquvchining o'zi tomonidan bekor qilinadi. Va xuddi for uchun bo'lgani kabi equals(), usul hashCode()Oracle hujjatlarida ko'rsatilgan rasmiy talablarga ega:
  1. Agar ikkita ob'ekt teng bo'lsa (ya'ni, usul equals()haqiqatni qaytarsa), ular bir xil xesh kodiga ega bo'lishi kerak.

    Aks holda bizning usullarimiz ma'nosiz bo'ladi. hashCode()Yuqorida aytib o'tganimizdek, ishlashni yaxshilash uchun birinchi navbatda tekshirish kerak. Agar xesh kodlari boshqacha bo'lsa, ob'ektlar aslida teng bo'lsa ham (biz usulda aniqlaganimizdek equals()) tekshiruv noto'g'ri bo'ladi.

  2. Agar usul hashCode()bir xil ob'ektda bir necha marta chaqirilsa, u har safar bir xil raqamni qaytarishi kerak.

  3. 1-qoida teskari ishlamaydi. Ikki xil ob'ekt bir xil xesh-kodga ega bo'lishi mumkin.

Uchinchi qoida biroz chalkash. Bu qanday bo'lishi mumkin? Tushuntirish juda oddiy. Usul hashCode()qaytadi int. int32 bitli raqam. U cheklangan miqdordagi qiymatlarga ega - -2,147,483,648 dan +2,147,483,647 gacha. Boshqacha qilib aytganda, raqamning 4 milliarddan ortiq o'zgarishi mavjud int. Endi tasavvur qiling-a, siz Yerdagi barcha tirik odamlar haqidagi ma'lumotlarni saqlash uchun dastur yaratyapsiz. Har bir inson o'z sinf ob'ektiga ega bo'ladi Man. Yer yuzida ~7,5 milliard odam yashaydi. Boshqacha qilib aytadigan bo'lsak, biz ob'ektlarni raqamlarga aylantirish uchun qanchalik yaxshi algoritm Manyozmasin, bizda raqamlar etarli bo'lmaydi. Bizda bor-yo'g'i 4,5 milliard variant va undan ko'p odamlar bor. Bu shuni anglatadiki, biz qanchalik qiyin bo'lishimizdan qat'iy nazar, xesh kodlari turli odamlar uchun bir xil bo'ladi. Bu holat (ikki xil ob'ektning xesh kodlari mos keladi) to'qnashuv deb ataladi. Usulni bekor qilishda dasturchining maqsadlaridan biri hashCode()mumkin bo'lgan to'qnashuvlar sonini iloji boricha kamaytirishdir. Ushbu qoidalarning barchasini hisobga olgan holda bizning hashCode()sinf uchun usulimiz qanday ko'rinishga ega bo'ladi? ManMana bunday:
@Override
public int hashCode() {
   return dnaCode;
}
Hayron qoldingizmi? :) Kutilmaganda, lekin talablarga qarasangiz, biz hamma narsaga rioya qilishimizni ko'rasiz. Bizniki equals()rost bo'lgan ob'ektlar teng bo'ladi hashCode(). Agar bizning ikkita ob'ektimiz Manqiymat jihatidan teng bo'lsa equals(ya'ni ular bir xil qiymatga ega bo'lsa dnaCode), bizning usulimiz bir xil raqamni qaytaradi. Keling, yanada murakkab misolni ko'rib chiqaylik. Aytaylik, bizning dasturimiz kollektor mijozlar uchun hashamatli avtomobillarni tanlashi kerak. Yig'ish murakkab narsa va uning ko'plab xususiyatlari bor. 1963 yildagi avtomobil 1964 yildagi mashinadan 100 baravar qimmatga tushishi mumkin. 1970 yildagi qizil avtomobil o'sha yildagi xuddi shu markadagi ko'k mashinadan 100 baravar qimmatga tushishi mumkin. Usullari teng & amp;  hashCode: foydalanish amaliyoti - 4Birinchi holda, sinf bilan Manbiz ko'pgina maydonlarni (ya'ni, shaxs xususiyatlarini) ahamiyatsiz deb tashladik va taqqoslash uchun faqat maydondan foydalandik dnaCode. Bu erda biz juda noyob hudud bilan ishlayapmiz va kichik tafsilotlar bo'lishi mumkin emas! Mana bizning sinfimiz 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.
}
Bu erda taqqoslashda biz barcha maydonlarni hisobga olishimiz kerak. Har qanday xato mijoz uchun yuz minglab dollarga tushishi mumkin, shuning uchun xavfsizroq bo'lish yaxshiroqdir:
@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);
}
Bizning usulimizda equals()biz ilgari aytib o'tgan barcha tekshiruvlarni unutmadik. Ammo endi biz ob'ektlarimizning uchta maydonining har birini solishtiramiz. Ushbu dasturda tenglik mutlaq, har bir sohada bo'lishi kerak. Nima haqida hashCode?
@Override
public int hashCode() {
   int result = model == null ? 0 : model.hashCode();
   result = result + manufactureYear;
   result = result + dollarPrice;
   return result;
}
Bizning sinfimizdagi maydon modelqatordir. Bu qulay: Stringusul hashCode()allaqachon sinfda bekor qilingan. Biz maydonning xesh kodini hisoblaymiz modelva unga boshqa ikkita raqamli maydonlar yig'indisini qo'shamiz. Java'da to'qnashuvlar sonini kamaytirish uchun ishlatiladigan kichik hiyla bor: xesh kodini hisoblashda oraliq natijani toq tub songa ko'paytiring. Eng ko'p ishlatiladigan raqam 29 yoki 31. Biz hozir matematikaning tafsilotlariga kirmaymiz, ammo kelajakda ma'lumot uchun, oraliq natijalarni etarlicha katta toq songa ko'paytirish xesh natijalarini "tarqatishga" yordam berishini unutmang. funktsiyasi va bir xil xeshkodli kamroq ob'ektlar bilan yakunlanadi. LuxuryAuto-dagi usulimiz uchun hashCode()u quyidagicha ko'rinadi:
@Override
public int hashCode() {
   int result = model == null ? 0 : model.hashCode();
   result = 31 * result + manufactureYear;
   result = 31 * result + dollarPrice;
   return result;
}
Ushbu mexanizmning barcha nozik tomonlarini StackOverflow-dagi ushbu postda , shuningdek, Joshua Blochning " Effektiv Java " kitobida o'qishingiz mumkin . Va nihoyat, yana bir muhim jihatni aytib o'tish kerak. Har safar bekor qilganda equals(), hashCode()biz ushbu usullarda hisobga olingan ob'ektning ma'lum maydonlarini tanladik. equals()Ammo va ichida turli sohalarni hisobga olamizmi hashCode()? Texnik jihatdan, biz qila olamiz. Ammo bu yomon fikr va buning sababi:
@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 sinfi equals()uchun bizning usullarimiz . hashCode()Usul hashCode()o'zgarishsiz qoldi va equals()biz maydonni usuldan olib tashladik model. Endi model ikkita ob'ektni bilan solishtirish uchun xarakteristika emas equals(). Ammo hash kodini hisoblashda u hali ham hisobga olinadi. Natijada nimaga erishamiz? Keling, ikkita mashina yaratamiz va uni tekshiramiz!
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
Xato! Turli maydonlardan foydalanib equals(), hashCode()biz ular uchun tuzilgan shartnomani buzdik! Ikkita teng equals()ob'ekt bir xil xesh-kodga ega bo'lishi kerak. Biz ular uchun turli xil ma'nolarni oldik. Bunday xatolar, ayniqsa, xeshlardan foydalanadigan to'plamlar bilan ishlashda eng aql bovar qilmaydigan oqibatlarga olib kelishi mumkin. Shuning uchun, qayta belgilashda equals()va hashCode()bir xil maydonlardan foydalanish to'g'ri bo'ladi. Ma'ruza juda uzoq bo'lib chiqdi, lekin bugun siz juda ko'p yangi narsalarni o'rgandingiz! :) Muammolarni hal qilishga qaytish vaqti keldi!
Izohlar
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION