equals
және hashCode
осы екі әдісті де өз сабақтарында дәйекті түрде ауыстырған жөн. Неліктен бұлай екенін және бұл ереже бұзылған жағдайда қандай қайғылы салдары болуы мүмкін екенін сәл азырақ сан біледі. Мен осы әдістердің тұжырымдамасын қарастыруды, олардың мақсатын қайталауды және олардың неге соншалықты байланысты екенін түсінуді ұсынамын. Мен бұл мақаланы, сыныптарды жүктеу туралы алдыңғы мақала сияқты, мәселенің барлық егжей-тегжейлерін ақырында ашып көрсету және енді үшінші тарап көздеріне оралмау үшін өзім үшін жаздым. Сондықтан мен сындарлы сынға қуаныштымын, өйткені бір жерде олқылықтар болса, оны жою керек. Мақала, өкінішке орай, өте ұзақ болып шықты.
қайта анықтау ережелеріне тең
Java тілінде бір шығу тегі екі нысанның логикалық теңequals()
екендігін растайтын немесе жоққа шығаратын әдіс қажет . Яғни, екі нысанды салыстыру кезінде бағдарламашы олардың маңызды өрістерінің эквивалентті екенін түсінуі керек . Барлық өрістердің бірдей болуы міндетті емес, өйткені әдіс логикалық теңдікті білдіреді . Бірақ кейде бұл әдісті қолданудың ерекше қажеттілігі болмайды. Олар айтқандай, белгілі бір механизмді пайдалану проблемаларын болдырмаудың ең оңай жолы - оны қолданбау. Сондай-ақ, сіз келісім-шартты бұзған кезде, сіз басқа нысандар мен құрылымдардың сіздің an objectімен қалай әрекеттесетінін түсінуді бақылауды жоғалтатыныңызды атап өткен жөн . Ал кейіннен қатенің себебін табу өте қиын болады. equals()
equals
Бұл әдісті қайта анықтауға болмайды
- Сыныптың әрбір данасы бірегей болғанда. Көбінесе бұл деректермен жұмыс істеуге арналған емес, нақты әрекетті қамтамасыз ететін сыныптарға қатысты. Мысалы, сынып сияқты
- Шындығында сыныпқа оның даналарының баламалылығын анықтау талап етілмейді. Мысалы, сынып үшін
- Сіз кеңейтіп жатқан сыныпта әдістің өзіндік іске асырылуы болған кезде
equals
және осы іске асыру әрекеті сізге сәйкес келеді. Мысалы, , сыныптары үшін equals
Ақырында, сіздің сыныпыңыздың ауқымы болған кезде қайта анықтаудың қажеті жоқprivate
немесеpackage-private
және сіз бұл әдіс ешқашан шақырылмайтынына сенімдісіз.
Thread
. Олар үшін equals
сынып ұсынған әдісті жүзеге асыру Object
жеткілікті. Тағы бір мысал - enum сыныптары ( Enum
).
java.util.Random
олардың кездейсоқ сандардың бірдей тізбегін қайтара алатындығын анықтай отырып, сынып даналарын бір-бірімен салыстырудың қажеті жоқ. Бұл сыныптың табиғаты мұндай мінез-құлықты білдірмейді.
Set
іске асыру сәйкесінше және ішінде List
болады . Map
equals
AbstractSet
AbstractList
AbstractMap
шартқа тең
Әдісті қайта анықтау кезіндеequals
әзірлеуші Java тілінің спецификациясында анықталған негізгі ережелерді сақтауы керек.
- Рефлексивтілік кез келген берілген мән үшін
- Симметрия кез келген берілген мәндер үшін
- Өтпелілік кез келген берілген мәндер үшін және
- Жүйелілік кез келген берілген мәндер үшін
- Салыстыру null кез келген берілген мән үшін
x
өрнек x.equals(x)
қайтарылуы керек true
.
Берілген – осылай дегенді білдіреді
x != null
x
және y
, қайтарылса ғана x.equals(y)
қайтарылуы керек . true
y.equals(x)
true
x
, егер қайтарса және қайтарса , мәнді қайтаруы керек . y
z
x.equals(y)
true
y.equals(z)
true
x.equals(z)
true
x
және y
қайталанатын шақыру x.equals(y)
екі нысанды салыстыру үшін пайдаланылатын өрістер қоңыраулар арасында өзгермеген жағдайда, алдыңғы шақырудың мәнін осы әдіске қайтарады.
x
қоңырау x.equals(null)
қайтарылуы керек false
.
шартты бұзуға тең
Java Collections Framework сияқты көптеген сыныптар әдісті іске асыруға байланыстыequals()
, сондықтан оны елемеуге болмайды, себебі Бұл әдістің шартын бұзу өтінімнің қисынсыз жұмыс істеуіне әкелуі мүмкін және бұл жағдайда оның себебін табу өте қиын болады. Рефлексивтілік принципі бойынша әрбір an object өзіне эквивалентті болуы керек. Егер бұл принцип бұзылса, коллекцияға an object қосып, содан кейін оны әдіс арқылы іздегенде, contains()
біз жинаққа жаңа қосқан нысанды таба алмаймыз. Симметрия шарты кез келген екі an object салыстыру ретіне қарамастан тең болуы керектігін айтады. equals
Мысалы, егер сізде жол түрінің тек бір өрісі бар сынып болса, бұл өрісті әдістегі жолмен салыстыру дұрыс емес болады . Өйткені кері салыстыру жағдайында әдіс әрқашан мәнді қайтарады 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);
}
Өтпелілік шартынан шығатыны, егер үш an objectінің кез келген екеуі тең болса, онда бұл жағдайда үшеуі де тең болуы керек. Бұл принцип оңай бұзылуы мүмкін, қашан қажет кеңейту белгілі бір базалық класс қосу арқылы оған мағыналы құрамдас . Point
Мысалы, координаттары бар сыныпқа x
және y
оны кеңейту арқылы нүктенің түсін қосу керек. ColorPoint
Мұны істеу үшін сізге сәйкес өрісі бар сыныпты жариялау қажет color
. Осылайша, егер кеңейтілген сыныпта біз equals
ата-аналық әдіс деп атасақ, ал ата-аналықта тек координаттар x
мен салыстырылады деп есептесек y
, онда әртүрлі түсті, бірақ координаталары бірдей екі нүкте тең деп есептеледі, бұл дұрыс емес. Бұл жағдайда туынды сыныпты түстерді ажыратуға үйрету керек. Мұны істеу үшін сіз екі әдісті пайдалана аласыз. Бірақ біреуі симметрия ережесін бұзады , ал екіншісі - транзитивтілік .
// Первый способ, нарушая симметричность
// Метод переопределен в классе ColorPoint
@Override
public boolean equals(Object o) {
if (!(o instanceof ColorPoint)) return false;
return super.equals(o) && ((ColorPoint) o).color == color;
}
Бұл жағдайда қоңырау point.equals(colorPoint)
мәнді қайтарады , ал true
салыстыру , өйткені «өз» класының an objectісін күтеді. Осылайша симметрия ережесі бұзылады. Екінші әдіс нүктенің түсі туралы деректер болмаған жағдайда «соқыр» тексеруді қамтиды, яғни бізде сынып бар . Немесе түс туралы ақпарат бар болса, оны тексеріңіз, яғни сыныптың нысанын салыстырыңыз . colorPoint.equals(point)
false
Point
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;
}
Бұл жерде транзитивтілік принципі келесідей бұзылады. Келесі an objectілердің анықтамасы бар делік:
ColorPoint p1 = new ColorPoint(1, 2, Color.RED);
Point p2 = new Point(1, 2);
ColorPoint p3 = new ColorPoint(1, 2, Color.BLUE);
p1.equals(p2)
Осылайша, және теңдігі орындалса да p2.equals(p3)
, p1.equals(p3)
ол мәнді қайтарады false
. Сонымен қатар, екінші әдіс, менің ойымша, аз тартымды көрінеді, өйткені Кейбір жағдайларда алгоритм соқыр болуы және салыстыруды толық орындамауы мүмкін және сіз бұл туралы білмеуіңіз мүмкін. Біраз поэзия Жалпы, менің түсінуімше, бұл мәселенің нақты шешімі жоқ. Кей Хорстманн есімді бір беделді автордың пікірі бар, сіз операторды пайдалануды нысанның сыныбын қайтаратын instanceof
әдіс шақыруымен ауыстыра аласыз getClass()
және an objectілердің өздерін салыстыруды бастамас бұрын олардың бір типті екеніне көз жеткізіңіз. , және олардың ортақ шығу фактісіне назар аудармаңыз. Осылайша, симметрия және транзитивтілік ережелері қанағаттандырылады. Бірақ сонымен бірге, баррикаданың арғы жағында, бұл тәсіл Барбара Лисковтың ауыстыру принципін бұзады деп санайтын, кең ортада одан кем емес тағы бір автор Джошуа Блох тұр. Бұл принцип «шақыру codeы базалық классты оның ішкі сыныптары сияқты білмей-ақ қарауы керек » деп көрсетеді . Ал Хорстман ұсынған шешімде бұл принцип анық бұзылған, өйткені ол іске асыруға байланысты. Қысқасы, істің күңгірт екені анық. Сондай-ақ, Хорстманның өз көзқарасын қолдану ережесін түсіндіретінін және сабақтарды құрастыру кезінде стратегия туралы шешім қабылдау керек екенін қарапайым ағылшын тілінде жазатынын атап өткен жөн, ал егер теңдік тестілеуді тек суперкласс жүргізетін болса, мұны орындау арқылы жасауға болады. операция instanceof
. Әйтпесе, тексерудің семантикасы туынды сыныпқа байланысты өзгергенде және әдісті жүзеге асыру иерархия бойынша төмен жылжыту қажет болғанда, әдісті пайдалану керек getClass()
. Джошуа Блох, өз кезегінде, мұрадан бас тартуды және сыныпқа ColorPoint
сыныпты қосу және нүкте туралы арнайы ақпаратты алу үшін Point
кіру әдісін қамтамасыз ету арқылы an object құрамын пайдалануды ұсынады. asPoint()
Бұл барлық ережелерді бұзудан аулақ болады, бірақ менің ойымша, бұл codeты түсінуді қиындатады. Үшінші нұсқа IDE көмегімен теңдік әдісін автоматты түрде жасауды пайдалану болып табылады. Айтпақшы, идея суперкласста немесе оның ұрпақтарында әдісті енгізу стратегиясын таңдауға мүмкіндік беретін Хорстман ұрпақтарын шығарады. Соңында, келесі бірізділік ережесі нысандар өзгермесе де x
, y
оларды қайта шақыру x.equals(y)
бұрынғыдай мәнді қайтару керек екенін айтады. Соңғы ереже - ешбір нысан -ге тең болмауы керек null
. Мұнда бәрі түсінікті null
– бұл белгісіздік, an object белгісіздікке тең бе? Бұл анық емес, яғни false
.
Теңдіктерді анықтаудың жалпы алгоритмі
this
Нысан сілтемелерінің және әдіс параметрлерінің теңдігін тексеріңізo
.if (this == o) return true;
- Сілтеменің анықталғанын
o
, яғни анықталғанын тексеріңізnull
.
Егер болашақта нысан түрлерін салыстыру кезінде оператор пайдаланылсаinstanceof
, бұл элементті өткізіп жіберуге болады, өйткені бұл параметрfalse
осы жағдайда қайтарыладыnull instanceof Object
. - Жоғарыдағы сипаттаманы және жеке интуицияны басшылыққа ала отырып, оператор немесе әдісті
this
пайдаланып нысан түрлерін салыстырыңыз .o
instanceof
getClass()
- Егер әдіс
equals
ішкі сыныпта қайта анықталса, қоңырау шалуды ұмытпаңызsuper.equals(o)
- Параметр түрін
o
қажетті классқа түрлендіріңіз. - Барлық маңызды нысан өрістерін салыстыруды орындаңыз:
- қарапайым түрлер үшін (
float
және қоспағандаdouble
) операторды пайдалана отырып==
- анықтамалық өрістер үшін олардың әдісін шақыру керек
equals
- массивтер үшін циклдік итерацияны немесе әдісті пайдалануға болады
Arrays.equals()
- түрлері үшін
float
жәнеdouble
сәйкес орауыш сыныптарының салыстыру әдістерін қолдану қажетFloat.compare()
жәнеDouble.compare()
- қарапайым түрлер үшін (
- Соңында үш сұраққа жауап беріңіз: енгізілген әдіс симметриялы ма ? Транзитивті ? Келісілді ме ? Қалған екі принцип ( рефлексивтілік және сенімділік ) әдетте автоматты түрде орындалады.
HashCode ережелерін қайта анықтау
Хэш - белгілі бір уақытта оның күйін сипаттайтын нысаннан жасалған сан. Бұл сан Java тілінде негізінен хэш-кестелерде пайдаланылады, мысалыHashMap
. Бұл жағдайда нысанға негізделген санды алудың хэш функциясы хэш кестесі бойынша элементтердің салыстырмалы түрде біркелкі таралуын қамтамасыз ететіндей жүзеге асырылуы керек. Сондай-ақ, функция әртүрлі пернелер үшін бірдей мәнді қайтарған кезде соқтығысу ықтималдығын азайту үшін.
Келісімшарт хэшcodeы
Хэш функциясын жүзеге асыру үшін тіл спецификациясы келесі ережелерді анықтайды:- бір нысанда әдісті
hashCode
бір немесе бірнеше рет шақыру мәнді есептеуге қатысатын нысан өрістері өзгермеген жағдайда, бірдей хэш мәнін қайтаруы керек. - екі нысандағы әдісті шақыру әрқашан бірдей санды қайтаруы керек, егер нысандар тең болса ( осы нысандардағы
hashCode
әдісті шақыру қайтарады ).equals
true
hashCode
екі тең емес нысанда әдісті шақыру әртүрлі хэш мәндерін қайтаруы керек. Бұл талап міндетті емес болса да, оның орындалуы хэш-кестелердің жұмысына оң әсер ететінін ескеру керек.
тең және hashCode әдістерін бірге қайта анықтау керек
Жоғарыда сипатталған келісім-шарттарға сүйене отырып, codeыңыздағы әдісті қайта анықтау кезіндеequals
сіз әрқашан әдісті қайта анықтауыңыз керек hashCode
. Шын мәнінде сыныптың екі данасы әртүрлі болғандықтан, олар әртүрлі жад аймақтарында болғандықтан, оларды кейбір логикалық критерийлерге сәйкес салыстыру керек. Сәйкесінше, екі логикалық баламалы нысан бірдей хэш мәнін қайтаруы керек. Осы әдістердің біреуі ғана қайта анықталса не болады?
-
equals
ИәhashCode
ЖоқБіз
equals
сыныбымызда әдісті дұрыс анықтадық делік жәнеhashCode
әдісті сыныптағыдай қалдыруды ұйғардықObject
. Сонда әдіс тұрғысынанequals
екі нысан логикалық тең болады, ал әдіс тұрғысынанhashCode
олардың ортақ ештеңесі болмайды. Осылайша, нысанды хэш-кестеге орналастыру арқылы біз оны кілт арқылы қайтарып алмау қаупін тудырамыз.
Мысалы, келесідей:Map<Point, String> m = new HashMap<>(); m.put(new Point(1, 1), “Point A”); // pointName == null String pointName = m.get(new Point(1, 1));
Орналастырылатын an object пен ізделетін an object логикалық жағынан тең болғанымен, екі түрлі нысан екені анық. Бірақ, өйткені оларда әртүрлі хэш мәндері бар, өйткені біз келісім-шартты бұздық, біз хэш кестесінің бір жерінде an objectімізді жоғалттық деп айта аламыз.
-
hashCode
Иәequals
Жоқ.Егер әдісті қайта анықтасақ
hashCode
жәнеequals
әдісті жүзеге асыруды сыныптан мұра етсек не боладыObject
. Өздеріңіз білетіндей,equals
әдепкі әдіс көрсеткіштерді an objectілермен салыстырады, олардың бір нысанға сілтеме жасайтынын анықтайды.hashCode
Біз әдісті барлық канондарға сәйкес жаздык деп есептейік , атап айтқанда, оны IDE көмегімен құрдық және ол логикалық бірдей нысандар үшін бірдей хэш мәндерін қайтарады. Әлбетте, осылайша біз екі нысанды салыстырудың кейбір механизмін анықтадық.Сондықтан алдыңғы абзацтағы мысалды теориялық түрде орындау керек. Бірақ біз әлі де хэш кестесінде нысанды таба алмаймыз. Біз бұған жақын боламыз, өйткені кем дегенде біз an object жататын хэш үстелінің себетін табамыз.
Хэш кестесіндегі нысанды сәтті іздеу үшін кілттің хэш мәндерін салыстырудан басқа, ізделетін нысанмен кілттің логикалық теңдігін анықтау да қолданылады. Яғни,
equals
әдісті жоққа шығармай, амал жоқ.
hashCode анықтаудың жалпы алгоритмі
Бұл жерде, менің ойымша, сіз тым көп уайымдамауыңыз керек және әдісті сүйікті IDE-де жасауыңыз керек. Өйткені алтын қатынасты іздеу үшін барлық осы биттердің оңға және солға жылжулары, яғни қалыпты бөлу - бұл мүлдем қыңыр жігіттерге арналған. Өз басым, мен бір идеядан жақсырақ және тезірек істей алатыныма күмәнданамын.Қорытындының орнына
Осылайша, әдістердің Java тілінде нақты анықталған рөлequals
атқаратынын және екі an objectінің логикалық теңдік сипаттамасын алуға арналғанын көреміз . hashCode
Әдіс жағдайында equals
бұл an objectілерді салыстыруға тікелей қатысы бар, hashCode
жанама жағдайда, қажет болғанда, айталық, хэш кестелерінде немесе ұқсас деректер құрылымдарында нысанның шамамен орналасуын анықтау үшін an objectіні іздеу жылдамдығын арттыру. Келісімшарттардан басқа , an objectілерді салыстыруға қатысты тағы бір талап бар equals
. Бұл интерфейс әдісінің hashCode
сәйкестігі . Бұл талап әзірлеушіні әрқашан қашан қайтаруға міндеттейді . Яғни, біз екі an objectіні логикалық салыстыру қосымшаның ешбір жерінде қайшы келмеуі және әрқашан сәйкес болуы керек екенін көреміз. compareTo
Comparable
equals
x.equals(y) == true
x.compareTo(y) == 0
GO TO FULL VERSION