equals
va hashCode
bu 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
- Darhaqiqat, sinf o'z misollarining ekvivalentligini aniqlashi shart emas. Masalan, sinf uchun
- Agar siz kengaytirayotgan sinf allaqachon o'ziga xos usulni qo'llashga ega bo'lsa
equals
va ushbu dasturning xatti-harakati sizga mos keladi. Masalan, , sinflari uchun amalga equals
Va nihoyat, sizning sinfingiz qamrovi qachonprivate
yoki bo'lsa , bekor qilishning hojati yo'qpackage-private
va bu usul hech qachon chaqirilmasligiga ishonchingiz komil.
Thread
. Ular uchun equals
sinf tomonidan taqdim etilgan usulni amalga oshirish Object
etarli. Yana bir misol - enum sinflari ( Enum
).
java.util.Random
bir 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.
Set
oshirish mos ravishda va ichida List
.Map
equals
AbstractSet
AbstractList
AbstractMap
shartnomaga teng
Usulni bekor qilishdaequals
ishlab chiquvchi Java tili spetsifikatsiyasida belgilangan asosiy qoidalarga amal qilishi kerak.
- Reflektorlik har qanday berilgan qiymat uchun
- Simmetriya har qanday berilgan qiymatlar uchun
- Tranzitivlik har qanday berilgan qiymatlar uchun
- Muvofiqlik har qanday berilgan qiymatlar uchun
- Taqqoslash null har qanday berilgan qiymat uchun
x
ifoda x.equals(x)
qaytishi kerak true
.
Berilgan - shunday degan ma'noni anglatadi
x != null
x
va y
, faqat qaytsa qaytishi x.equals(y)
kerak . true
y.equals(x)
true
x
, y
va z
, agar x.equals(y)
qaytaradi true
va y.equals(z)
qaytarsa true
, x.equals(z)
qiymatini qaytarish kerak true
.
x
va y
takroriy 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.
x
qo'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. equals
Misol 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 . Point
Masalan, koordinatalari bo'lgan sinfga x
va y
siz uni kengaytirish orqali nuqta rangini qo'shishingiz kerak. Buning uchun ColorPoint
tegishli maydonga ega sinfni e'lon qilishingiz kerak bo'ladi color
. Shunday qilib, agar kengaytirilgan sinfda biz equals
ota-ona usulini chaqirsak va ota-onada biz faqat koordinatalar x
va 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 instanceof
usul 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 ColorPoint
sinfni kiritish va nuqta haqida maxsus ma'lumot olish uchun Point
kirish 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
, y
ularni 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
this
Ob'ekt havolalari va usul parametrlarining tengligini tekshiringo
.if (this == o) return true;
- Havola belgilangan yoki
o
yo'qligini tekshiring, ya'ninull
.
Agar kelajakda ob'ekt turlarini taqqoslashda operator ishlatilsa,instanceof
ushbu elementni o'tkazib yuborish mumkin, chunki bu parametrfalse
bu holatda qaytariladinull instanceof Object
. - Yuqoridagi tavsif va o'z sezgiingizga asoslanib, operator yoki usul
this
yordamida ob'ekt turlarini solishtiring .o
instanceof
getClass()
- Agar usul
equals
quyi sinfda bekor qilingan bo'lsa, qo'ng'iroq qilishni unutmangsuper.equals(o)
- Parametr turini
o
kerakli sinfga aylantiring. - Barcha muhim ob'ekt maydonlarini taqqoslang:
- ibtidoiy turlar uchun (
float
va dan tashqaridouble
) operator yordamida==
- mos yozuvlar maydonlari uchun siz ularning usulini chaqirishingiz kerak
equals
- massivlar uchun siz tsiklik iteratsiya yoki usuldan foydalanishingiz mumkin
Arrays.equals()
- turlari bo'yicha
float
vadouble
tegishli o'rash sinflarining taqqoslash usullarini qo'llash kerakFloat.compare()
vaDouble.compare()
- ibtidoiy turlar uchun (
- 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, masalanHashMap
. 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
hashCode
bir yoki bir necha marta chaqirish, qiymatni hisoblashda ishtirok etadigan ob'ekt maydonlari o'zgarmagan bo'lsa, bir xil xesh qiymatini qaytarishi kerak. hashCode
Agar ob'ektlar teng bo'lsa, ikkita ob'ektda usulni chaqirish har doim bir xil raqamni qaytarishi kerak (equals
bu ob'ektlardagi usulni chaqirish qaytaraditrue
).hashCode
ikkita 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 qilgandaequals
, 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?
-
equals
HahashCode
yo'qAytaylik, biz
equals
sinfimizda metodni to'g'ri belgilab oldik vahashCode
usulni sinfdagi kabi qoldirishga qaror qildikObject
. Usul nuqtai nazaridanequals
ikkala ob'ekt mantiqiy jihatdan teng bo'ladi, usul nuqtai nazaridan esahashCode
ularda 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.
-
hashCode
Haequals
yo'q.Agar biz usulni bekor qilsak
hashCode
vaequals
usulning amalga oshirilishini sinfdan meros qilib olsak nima bo'ladiObject
. Ma'lumki,equals
standart usul shunchaki ko'rsatgichlarni ob'ektlar bilan taqqoslaydi va ular bir xil ob'ektga murojaat qiladimi yoki yo'qligini aniqlaydi. Faraz qilaylik,hashCode
biz 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,
equals
usulni 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 rolequals
o'ynashini va ikkita ob'ektning mantiqiy tenglik xususiyatini olish uchun mo'ljallanganligini ko'ramiz . hashCode
Usul bo'lsa, equals
bu ob'ektlarni taqqoslash bilan bevosita bog'liq, hashCode
bilvosita 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 hashCode
muvofiqligi . 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. compareTo
Comparable
equals
x.equals(y) == true
x.compareTo(y) == 0
GO TO FULL VERSION