JavaRush /Java blogi /Random-UZ /Teng va hashCode shartnomalari yoki nima bo'lishidan qat'...
Aleksandr Zimin
Daraja
Санкт-Петербург

Teng va hashCode shartnomalari yoki nima bo'lishidan qat'iy nazar

Guruhda nashr etilgan
Java dasturchilarining katta qismi, albatta, usullar bir-biri bilan chambarchas bog'liqligini bilishadi equalsva hashCodebu ikkala usulni o'z sinflarida izchil ravishda bekor qilish tavsiya etiladi. Bir oz kichikroq raqam nima uchun bunday ekanligini va bu qoida buzilgan taqdirda qanday qayg'uli oqibatlarga olib kelishi mumkinligini biladi. Men ushbu usullarning kontseptsiyasini ko'rib chiqishni, ularning maqsadlarini takrorlashni va nima uchun ular shunchalik bog'liqligini tushunishni taklif qilaman. Men ushbu maqolani, avvalgidek, darslarni yuklash haqida, masalaning barcha tafsilotlarini oxirigacha ochib berish va endi uchinchi tomon manbalariga qaytmaslik uchun o'zim uchun yozdim. Shuning uchun men konstruktiv tanqiddan xursand bo'laman, chunki biror joyda bo'shliqlar bo'lsa, ularni yo'q qilish kerak. Maqola, afsuski, juda uzun bo'lib chiqdi.

bekor qilish qoidalariga teng

equals()Java'da bir xil kelib chiqishi ikkita ob'ektning mantiqiy jihatdan teng ekanligini tasdiqlash yoki rad etish uchun usul talab qilinadi . Ya'ni, ikkita ob'ektni solishtirganda, dasturchi ularning muhim maydonlari ekvivalentligini tushunishi kerak . Barcha maydonlar bir xil bo'lishi shart emas, chunki usul mantiqiy tenglikniequals() nazarda tutadi . Ammo ba'zida bu usuldan foydalanishga alohida ehtiyoj yo'q. Ular aytganidek, ma'lum bir mexanizmdan foydalanishda muammolardan qochishning eng oson yo'li uni ishlatmaslikdir. Shuni ham ta'kidlash kerakki, shartnomani buzganingizdan so'ng, siz boshqa ob'ektlar va tuzilmalar sizning ob'ektingiz bilan qanday o'zaro ta'sir qilishini tushunish nazoratini yo'qotasiz. Va keyinchalik xatoning sababini topish juda qiyin bo'ladi. equals

Qachon bu usulni bekor qilmaslik kerak

  • Sinfning har bir namunasi noyob bo'lsa.
  • Ko'proq darajada, bu ma'lumotlar bilan ishlash uchun mo'ljallangan emas, balki o'ziga xos xatti-harakatlarni ta'minlaydigan sinflarga tegishli. Masalan, sinf kabi Thread. Ular uchun equalssinf tomonidan taqdim etilgan usulni amalga oshirish Objectetarli. Yana bir misol - enum sinflari ( Enum).
  • Darhaqiqat, sinf o'z misollarining ekvivalentligini aniqlashi shart emas.
  • Masalan, sinf uchun java.util.Randombir xil tasodifiy sonlar ketma-ketligini qaytarish mumkinligini aniqlash uchun sinf misollarini bir-biri bilan solishtirishning hojati yo'q. Shunchaki, bu sinfning tabiati hatto bunday xatti-harakatni anglatmaydi.
  • Agar siz kengaytirayotgan sinf allaqachon o'ziga xos usulni qo'llashga ega bo'lsa equalsva ushbu dasturning xatti-harakati sizga mos keladi.
  • Masalan, , sinflari uchun amalga Setoshirish mos ravishda va ichida List.MapequalsAbstractSetAbstractListAbstractMap
  • equalsVa nihoyat, sizning sinfingiz qamrovi qachon privateyoki bo'lsa , bekor qilishning hojati yo'q package-privateva bu usul hech qachon chaqirilmasligiga ishonchingiz komil.

shartnomaga teng

Usulni bekor qilishda equalsishlab chiquvchi Java tili spetsifikatsiyasida belgilangan asosiy qoidalarga amal qilishi kerak.
  • Reflektorlik
  • har qanday berilgan qiymat uchun xifoda x.equals(x)qaytishi kerak true.
    Berilgan - shunday degan ma'noni anglatadix != null
  • Simmetriya
  • har qanday berilgan qiymatlar uchun xva y, faqat qaytsa qaytishi x.equals(y)kerak . truey.equals(x)true
  • Tranzitivlik
  • har qanday berilgan qiymatlar uchun x, yva z, agar x.equals(y)qaytaradi trueva y.equals(z)qaytarsa true, x.equals(z)qiymatini qaytarish kerak true.
  • Muvofiqlik
  • har qanday berilgan qiymatlar uchun xva ytakroriy qo'ng'iroq x.equals(y)bu usulga oldingi qo'ng'iroqning qiymatini qaytaradi, agar ikkita ob'ektni solishtirish uchun ishlatiladigan maydonlar qo'ng'iroqlar orasida o'zgarmasa.
  • Taqqoslash null
  • har qanday berilgan qiymat uchun xqo'ng'iroq x.equals(null)qaytishi kerak false.

shartnoma buzilishiga teng

Java Collections Framework-dagi kabi ko'plab sinflar usulni amalga oshirishga bog'liq, equals()shuning uchun uni e'tiborsiz qoldirmaslik kerak, chunki Ushbu usul bo'yicha shartnomani buzish arizaning mantiqsiz ishlashiga olib kelishi mumkin va bu holda sababni topish juda qiyin bo'ladi. Reflektorlik printsipiga ko'ra , har bir ob'ekt o'ziga ekvivalent bo'lishi kerak. Agar bu tamoyil buzilgan bo'lsa, ob'ektni to'plamga qo'shib, keyin uni usul yordamida qidirganimizda, contains()biz to'plamga hozirgina qo'shgan ob'ektimizni topa olmaymiz. Simmetriya sharti shuni ko'rsatadiki, har qanday ikkita ob'ekt solishtirilish tartibidan qat'i nazar, teng bo'lishi kerak. equalsMisol uchun, agar sizda faqat bitta satr turi maydonini o'z ichiga olgan sinf mavjud bo'lsa, bu maydonni usuldagi satr bilan solishtirish noto'g'ri bo'ladi . Chunki teskari taqqoslashda usul har doim qiymatni qaytaradi false.
// Нарушение симметричности
public class SomeStringify {
    private String s;

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o instanceof SomeStringify) {
            return s.equals(((SomeStringify) o).s);
        }
        // нарушение симметричности, классы разного происхождения
        if (o instanceof String) {
            return s.equals(o);
        }
        return false;
    }
}
//Правильное определение метода equals
@Override
public boolean equals(Object o) {
    if (this == o) return true;
    return o instanceof SomeStringify &&
            ((SomeStringify) o).s.equals(s);
}
Tranzitivlik shartidan kelib chiqadiki, agar uchta ob'ektdan ikkitasi teng bo'lsa, bu holda uchtasi ham teng bo'lishi kerak. Ma'lum bir asosiy sinfga mazmunli komponent qo'shish orqali kengaytirish zarur bo'lganda, bu tamoyil osongina buzilishi mumkin . PointMasalan, koordinatalari bo'lgan sinfga xva ysiz uni kengaytirish orqali nuqta rangini qo'shishingiz kerak. Buning uchun ColorPointtegishli maydonga ega sinfni e'lon qilishingiz kerak bo'ladi color. Shunday qilib, agar kengaytirilgan sinfda biz equalsota-ona usulini chaqirsak va ota-onada biz faqat koordinatalar xva taqqoslangan deb hisoblasak y, u holda turli rangdagi, lekin bir xil koordinatali ikkita nuqta teng deb hisoblanadi, bu noto'g'ri. Bunday holda, olingan sinfni ranglarni farqlashga o'rgatish kerak. Buning uchun siz ikkita usuldan foydalanishingiz mumkin. Lekin biri simmetriya qoidasini buzadi , ikkinchisi esa - tranzitivlik .
// Первый способ, нарушая симметричность
// Метод переопределен в классе ColorPoint
@Override
public boolean equals(Object o) {
    if (!(o instanceof ColorPoint)) return false;
    return super.equals(o) && ((ColorPoint) o).color == color;
}
Bunday holda, qo'ng'iroq point.equals(colorPoint)qiymatini qaytaradi true, va taqqoslash colorPoint.equals(point)qaytadi false, chunki "o'z" sinfining ob'ektini kutadi. Shunday qilib, simmetriya qoidasi buziladi. Ikkinchi usul nuqta rangi haqida hech qanday ma'lumot yo'q bo'lsa, "ko'r" tekshiruvni o'z ichiga oladi, ya'ni bizda sinf mavjud Point. Yoki rangni tekshiring, agar u haqida ma'lumot mavjud bo'lsa, ya'ni sinf ob'ektini solishtiring ColorPoint.
// Метод переопределен в классе ColorPoint
@Override
public boolean equals(Object o) {
    if (!(o instanceof Point)) return false;

    // Слепая проверка
    if (!(o instanceof ColorPoint))
        return super.equals(o);

    // Полная проверка, включая цвет точки
    return super.equals(o) && ((ColorPoint) o).color == color;
}
Bu erda tranzitivlik printsipi quyidagicha buziladi. Aytaylik, quyidagi ob'ektlarning ta'rifi mavjud:
ColorPoint p1 = new ColorPoint(1, 2, Color.RED);
Point p2 = new Point(1, 2);
ColorPoint p3 = new ColorPoint(1, 2, Color.BLUE);
Shunday qilib, tenglik p1.equals(p2)va qoniqtirilsa-da p2.equals(p3), p1.equals(p3)u qiymatni qaytaradi false. Shu bilan birga, ikkinchi usul, mening fikrimcha, kamroq jozibali ko'rinadi, chunki Ba'zi hollarda, algoritm ko'r bo'lishi mumkin va taqqoslashni to'liq bajarmaydi va siz bu haqda bilmasligingiz mumkin. Biroz she'r Umuman, men tushunganimdek, bu muammoning aniq yechimi yo'q. Kay Horstman ismli bir nufuzli muallifning fikri bor, siz operatordan foydalanishni ob'ekt sinfini qaytaradigan instanceofusul chaqiruvi bilan almashtirishingiz mumkin va ob'ektlarning o'zlarini solishtirishni boshlashdan oldin, ularning bir xil turdagi ekanligiga ishonch hosil qiling. getClass(), va ularning umumiy kelib chiqishi faktiga e'tibor bermang. Shunday qilib, simmetriya va tranzitivlik qoidalari qondiriladi. Ammo shu bilan birga, barrikadaning narigi tomonida keng doiralarda hurmatga sazovor bo'lmagan yana bir muallif Joshua Bloch turadi, bu yondashuv Barbara Liskovning almashtirish tamoyilini buzadi, deb hisoblaydi. Ushbu tamoyilda aytilishicha, "qo'ng'iroq kodi asosiy sinfga uning pastki sinflari kabi, uni bilmagan holda muomala qilishi kerak " . Va Horstmann tomonidan taklif qilingan yechimda bu tamoyil aniq buzilgan, chunki bu amalga oshirishga bog'liq. Muxtasar qilib aytganda, masala qorong'i ekanligi aniq. Shuni ham ta'kidlash kerakki, Horstmann o'z yondashuvini qo'llash qoidasini aniqlab beradi va oddiy ingliz tilida darslarni loyihalashda strategiya to'g'risida qaror qabul qilishingiz kerakligini yozadi va agar tenglik testi faqat yuqori sinf tomonidan amalga oshirilsa, buni amalga oshirish orqali qilishingiz mumkin. operatsiya instanceof. Aks holda, tekshirishning semantikasi olingan sinfga qarab o'zgarganda va usulni amalga oshirish ierarxiyadan pastga siljishi kerak bo'lganda, siz usuldan foydalanishingiz kerak getClass(). Joshua Bloch, o'z navbatida, merosdan voz kechishni va sinfga ColorPointsinfni kiritish va nuqta haqida maxsus ma'lumot olish uchun Pointkirish usulini taqdim etish orqali ob'ekt tarkibidan foydalanishni taklif qiladi. asPoint()Bu barcha qoidalarni buzishdan qochadi, lekin mening fikrimcha, bu kodni tushunishni qiyinlashtiradi. Uchinchi variant - IDE yordamida tenglashtirish usulini avtomatik yaratishdan foydalanish. Aytgancha, g'oya Horstmann avlodini takrorlaydi, bu sizga supersinfda yoki uning avlodlarida usulni amalga oshirish strategiyasini tanlash imkonini beradi. Nihoyat, keyingi izchillik qoidasi shuni ko'rsatadiki, ob'ektlar o'zgarmasa ham x, yularni qayta chaqirish x.equals(y)avvalgidek bir xil qiymatni qaytarishi kerak. Yakuniy qoida shundaki, hech qanday ob'ekt ga teng bo'lmasligi kerak null. Bu erda hamma narsa aniq null- bu noaniqlik, ob'ekt noaniqlikka tengmi? Bu aniq emas, ya'ni false.

Tengliklarni aniqlashning umumiy algoritmi

  1. thisOb'ekt havolalari va usul parametrlarining tengligini tekshiring o.
    if (this == o) return true;
  2. Havola belgilangan yoki oyo'qligini tekshiring, ya'ni null.
    Agar kelajakda ob'ekt turlarini taqqoslashda operator ishlatilsa, instanceofushbu elementni o'tkazib yuborish mumkin, chunki bu parametr falsebu holatda qaytariladi null instanceof Object.
  3. Yuqoridagi tavsif va o'z sezgiingizga asoslanib, operator yoki usul thisyordamida ob'ekt turlarini solishtiring .oinstanceofgetClass()
  4. Agar usul equalsquyi sinfda bekor qilingan bo'lsa, qo'ng'iroq qilishni unutmangsuper.equals(o)
  5. Parametr turini okerakli sinfga aylantiring.
  6. Barcha muhim ob'ekt maydonlarini taqqoslang:
    • ibtidoiy turlar uchun ( floatva dan tashqari double) operator yordamida==
    • mos yozuvlar maydonlari uchun siz ularning usulini chaqirishingiz kerakequals
    • massivlar uchun siz tsiklik iteratsiya yoki usuldan foydalanishingiz mumkinArrays.equals()
    • turlari bo'yicha floatva doubletegishli o'rash sinflarining taqqoslash usullarini qo'llash kerak Float.compare()vaDouble.compare()
  7. Va nihoyat, uchta savolga javob bering: amalga oshirilgan usul nosimmetrikmi ? O'tish davri ? Kelishilganmisiz ? Qolgan ikkita printsip ( reflektorlik va aniqlik ) odatda avtomatik ravishda amalga oshiriladi.

HashCode qoidalarini bekor qilish

Xesh - bu ob'ektdan hosil bo'lgan raqam bo'lib, uning ma'lum bir vaqtdagi holatini tavsiflaydi. Bu raqam Java-da asosan xesh-jadvallarda ishlatiladi, masalan HashMap. Bunday holda, ob'ektga asoslangan raqamni olishning xesh funktsiyasi elementlarning xesh jadvali bo'ylab nisbatan teng taqsimlanishini ta'minlaydigan tarzda amalga oshirilishi kerak. Shuningdek, funktsiya turli tugmalar uchun bir xil qiymatni qaytarganda to'qnashuvlar ehtimolini kamaytirish uchun.

Shartnoma hashkodi

Xesh funksiyasini amalga oshirish uchun til spetsifikatsiyasi quyidagi qoidalarni belgilaydi:
  • bir xil ob'ektda usulni hashCodebir yoki bir necha marta chaqirish, qiymatni hisoblashda ishtirok etadigan ob'ekt maydonlari o'zgarmagan bo'lsa, bir xil xesh qiymatini qaytarishi kerak.
  • hashCodeAgar ob'ektlar teng bo'lsa, ikkita ob'ektda usulni chaqirish har doim bir xil raqamni qaytarishi kerak ( equalsbu ob'ektlardagi usulni chaqirish qaytaradi true).
  • hashCodeikkita teng bo'lmagan ob'ektda usulni chaqirish turli xil xesh qiymatlarini qaytarishi kerak. Garchi bu talab majburiy bo'lmasa-da, uni amalga oshirish xesh-jadvallarning ishlashiga ijobiy ta'sir ko'rsatishini hisobga olish kerak.

Teng va hashCode usullari birgalikda bekor qilinishi kerak

Yuqorida tavsiflangan shartnomalarga asoslanib, kodingizdagi usulni bekor qilganda equals, har doim usulni bekor qilishingiz kerak hashCode. Darhaqiqat, sinfning ikkita misoli turli xil xotira sohalarida bo'lganligi sababli, ularni ba'zi mantiqiy mezonlarga ko'ra solishtirish kerak. Shunga ko'ra, ikkita mantiqiy ekvivalent ob'ektlar bir xil xesh qiymatini qaytarishi kerak. Agar ushbu usullardan faqat bittasi bekor qilinsa nima bo'ladi?
  1. equalsHa hashCodeyo'q

    Aytaylik, biz equalssinfimizda metodni to'g'ri belgilab oldik va hashCodeusulni sinfdagi kabi qoldirishga qaror qildik Object. Usul nuqtai nazaridan equalsikkala ob'ekt mantiqiy jihatdan teng bo'ladi, usul nuqtai nazaridan esa hashCodeularda umumiylik bo'lmaydi. Shunday qilib, ob'ektni xesh-jadvalga joylashtirish orqali biz uni kalit bilan qaytarib olmaslik xavfiga duch kelamiz.
    Masalan, bu kabi:

    Map<Point, String> m = new HashMap<>();
    m.put(new Point(1, 1),Point A);
    // pointName == null
    String pointName = m.get(new Point(1, 1));

    Shubhasiz, joylashtirilayotgan ob'ekt va qidirilayotgan ob'ekt ikki xil ob'ektdir, garchi ular mantiqan teng bo'lsa ham. Lekin, chunki ular har xil xesh qiymatlariga ega, chunki biz shartnomani buzganimiz sababli, biz o'z ob'ektimizni xesh jadvalining ichaklarida yo'qotib qo'yganimizni aytishimiz mumkin.

  2. hashCodeHa equalsyo'q.

    Agar biz usulni bekor qilsak hashCodeva equalsusulning amalga oshirilishini sinfdan meros qilib olsak nima bo'ladi Object. Ma'lumki, equalsstandart usul shunchaki ko'rsatgichlarni ob'ektlar bilan taqqoslaydi va ular bir xil ob'ektga murojaat qiladimi yoki yo'qligini aniqlaydi. Faraz qilaylik, hashCodebiz usulni barcha qonunlarga muvofiq yozdik, ya'ni uni IDE yordamida yaratdik va u mantiqan bir xil ob'ektlar uchun bir xil xesh qiymatlarini qaytaradi. Shubhasiz, bu bilan biz ikkita ob'ektni solishtirish uchun qandaydir mexanizmni belgilab oldik.

    Shuning uchun, oldingi paragrafdagi misol nazariy jihatdan amalga oshirilishi kerak. Lekin biz hali ham xesh jadvalida ob'ektimizni topa olmaymiz. Garchi biz bunga yaqin bo'lamiz, chunki hech bo'lmaganda biz ob'ekt yotadigan hash stol savatini topamiz.

    Xesh-jadvalda ob'ektni muvaffaqiyatli qidirish uchun kalitning xesh qiymatlarini solishtirishdan tashqari, qidiruv ob'ekti bilan kalitning mantiqiy tengligini aniqlash ham qo'llaniladi. Ya'ni, equalsusulni bekor qilmasdan qilishning iloji yo'q.

HashCode ni aniqlashning umumiy algoritmi

Bu erda, menimcha, siz juda ko'p tashvishlanmasligingiz va o'zingizning sevimli IDE-da usulni yaratishingiz kerak. Chunki oltin nisbatni izlash uchun bitlarning o'ngga va chapga siljishi, ya'ni normal taqsimot - bu butunlay o'jar dudlar uchun. Shaxsan men bir xil Ideyadan yaxshiroq va tezroq qila olishimga shubha qilaman.

Xulosa o'rniga

Shunday qilib, usullar Java tilida aniq belgilangan rol equalso'ynashini va ikkita ob'ektning mantiqiy tenglik xususiyatini olish uchun mo'ljallanganligini ko'ramiz . hashCodeUsul bo'lsa, equalsbu ob'ektlarni taqqoslash bilan bevosita bog'liq, hashCodebilvosita bo'lsa, kerak bo'lganda, aytaylik, ob'ektning xesh jadvallari yoki shunga o'xshash ma'lumotlar tuzilmalarida taxminiy joylashishini aniqlash uchun. ob'ektni qidirish tezligini oshirish. Shartnomalarga qo'shimcha ravishda , ob'ektlarni taqqoslash bilan bog'liq yana bir talab mavjud equals. Bu interfeys usulining a bilan hashCodemuvofiqligi . Bu talab ishlab chiquvchini har doim qachon qaytishga majbur qiladi . Ya'ni, ikkita ob'ektni mantiqiy taqqoslash ilovaning hech bir joyiga zid kelmasligi va doimo izchil bo'lishi kerakligini ko'ramiz. compareToComparableequalsx.equals(y) == truex.compareTo(y) == 0

Manbalar

Samarali Java, Ikkinchi nashr. Joshua Bloch. Juda yaxshi kitobning bepul tarjimasi. Java, professional kutubxona. 1-jild. Asoslar. Kay Horstmann. Bir oz kamroq nazariya va ko'proq amaliyot. Ammo hamma narsa Blochniki kabi batafsil tahlil qilinmagan. Garchi bir xil teng () bo'yicha ko'rinish mavjud bo'lsa-da. Rasmlardagi ma'lumotlar tuzilmalari. HashMap Java-dagi HashMap qurilmasi haqida juda foydali maqola. Manbalarga qarash o'rniga.
Izohlar
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION