equals
və hashCode
bu metodların hər ikisini öz dərslərində ardıcıl olaraq ləğv etmək məqsədəuyğundur. Bir az daha kiçik bir rəqəm bunun niyə belə olduğunu və bu qayda pozulduğu təqdirdə hansı kədərli nəticələrə səbəb ola biləcəyini bilir. Bu metodların konsepsiyasını nəzərdən keçirməyi, məqsədlərini təkrarlamağı və niyə bu qədər əlaqəli olduğunu başa düşməyi təklif edirəm. Bu məqaləni, dərslərin yüklənməsi ilə bağlı əvvəlki kimi, nəhayət məsələnin bütün təfərrüatlarını ortaya çıxarmaq və artıq üçüncü tərəf mənbələrinə qayıtmamaq üçün özüm üçün yazdım. Ona görə də konstruktiv tənqidə şad olaram, çünki haradasa boşluqlar varsa, onları aradan qaldırmaq lazımdır. Məqalə, təəssüf ki, kifayət qədər uzun oldu.
Qaydaları ləğv etməyə bərabərdir
equals()
Java-da eyni mənşəli iki obyektin məntiqi olaraq bərabər olduğunu təsdiqləmək və ya inkar etmək üçün metod tələb olunur . Yəni, iki obyekti müqayisə edərkən proqramçı onların əhəmiyyətli sahələrinin ekvivalent olub olmadığını başa düşməlidir . Metod məntiqi bərabərliyiequals()
nəzərdə tutduğundan bütün sahələrin eyni olması vacib deyil . Ancaq bəzən bu üsuldan istifadə etməyə xüsusi ehtiyac yoxdur. Necə deyərlər, müəyyən bir mexanizmdən istifadə etməklə problemlərdən qaçmağın ən asan yolu ondan istifadə etməməkdir. Onu da qeyd etmək lazımdır ki, müqaviləni pozduqdan sonra siz digər obyektlərin və strukturların obyektinizlə necə qarşılıqlı əlaqədə olacağını anlamaqda nəzarəti itirirsiniz. Və sonradan səhvin səbəbini tapmaq çox çətin olacaq. equals
Bu metodu ləğv etməmək üçün
- Bir sinfin hər bir nümunəsi unikal olduqda. Daha çox dərəcədə bu, verilənlərlə işləmək üçün nəzərdə tutulmaqdansa, xüsusi davranışı təmin edən siniflərə aiddir. Məsələn, sinif kimi
- Əslində sinifdən nümunələrinin ekvivalentliyini müəyyən etmək tələb olunmadığı zaman. Məsələn, bir sinif üçün,
- Genişləndirdiyiniz sinif artıq metodun öz tətbiqinə malik olduqda
equals
və bu tətbiqin davranışı sizə uyğun gəlir. Məsələn, , sinifləri üçün həyata equals
Və nəhayət, sinifinizin əhatə dairəsiprivate
və ya nə vaxt olduğunu ləğv etməyə ehtiyac yoxdurpackage-private
və bu metodun heç vaxt çağırılmayacağına əminsiniz.
Thread
. Onlar üçün equals
sinfin təqdim etdiyi metodun həyata keçirilməsi Object
kifayət qədərdir. Başqa bir misal enum sinifləridir ( Enum
).
java.util.Random
eyni təsadüfi ədədlər ardıcıllığını qaytara bilməyəcəyini təyin edərək, sinif nümunələrini bir-biri ilə müqayisə etməyə ehtiyac yoxdur. Sadəcə, çünki bu sinfin təbiəti belə davranışı nəzərdə tutmur.
Set
keçirilməsi müvafiq olaraq , və içərisindədir . List
Map
equals
AbstractSet
AbstractList
AbstractMap
müqaviləyə bərabərdir
Bir metodu ləğv edərkən,equals
tərtibatçı Java dilinin spesifikasiyasında müəyyən edilmiş əsas qaydalara riayət etməlidir.
- Refleksivlik hər hansı verilmiş dəyər üçün
- Simmetriya hər hansı verilmiş dəyər üçün
- Keçidlilik hər hansı verilmiş dəyərlər üçün
- Ardıcıllıq hər hansı verilmiş qiymətlər üçün
- Müqayisə null hər hansı bir dəyər üçün
x
ifadə x.equals(x)
qayıtmalıdır true
.
Verilmiş - belə mənada
x != null
x
və y
, yalnız qayıtdıqda x.equals(y)
qayıtmalıdır . true
y.equals(x)
true
x
və y
əgər z
qaytarırsa və qaytarırsa x.equals(y)
, dəyəri qaytarmalıdır . true
y.equals(z)
true
x.equals(z)
true
x
və y
təkrar edilən zəng x.equals(y)
əvvəlki çağırışın dəyərini bu metoda qaytaracaq, bir şərtlə ki, iki obyekti müqayisə etmək üçün istifadə olunan sahələr zənglər arasında dəyişməyib.
x
zəng x.equals(null)
geri qayıtmalıdır false
.
müqavilənin pozulmasına bərabərdir
Java Collections Framework-dən olanlar kimi bir çox siniflər metodun həyata keçirilməsindən asılıdırequals()
, ona görə də onu laqeyd etməməlisiniz, çünki Bu metodun müqaviləsinin pozulması ərizənin irrasional işləməsinə səbəb ola bilər və bu halda səbəbi tapmaq olduqca çətin olacaq. Refleks prinsipinə görə , hər bir obyekt özünə ekvivalent olmalıdır. Bu prinsip pozularsa, biz kolleksiyaya obyekt əlavə etdikdə və sonra metoddan istifadə edərək onu axtaranda, contains()
indicə kolleksiyaya əlavə etdiyimiz obyekti tapa bilməyəcəyik. Simmetriya şərti bildirir ki, hər hansı iki obyekt müqayisə olunma ardıcıllığından asılı olmayaraq bərabər olmalıdır. equals
Məsələn, sətir tipli yalnız bir sahəni ehtiva edən sinifiniz varsa, bu sahəni metoddakı sətirlə müqayisə etmək düzgün olmaz . Çünki tərs müqayisə vəziyyətində, metod həmişə dəyəri qaytaracaq 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);
}
Keçidlilik şərtindən belə çıxır ki, üç obyektdən hər hansı ikisi bərabərdirsə, bu halda hər üçü bərabər olmalıdır. Müəyyən bir baza sinfinə mənalı bir komponent əlavə etməklə genişləndirmək lazım olduqda bu prinsip asanlıqla pozula bilər . Məsələn, Point
koordinatları olan bir sinifə x
və y
onu genişləndirərək nöqtənin rəngini əlavə etməlisiniz. ColorPoint
Bunu etmək üçün müvafiq sahə ilə bir sinif elan etməlisiniz color
. Beləliklə, əgər genişləndirilmiş sinifdə biz equals
ana metodu çağırırıqsa və valideyndə yalnız koordinatların x
və müqayisə edildiyini fərz etsək y
, onda müxtəlif rəngli, lakin eyni koordinatlara malik iki nöqtə bərabər hesab ediləcək, bu səhvdir. Bu zaman törəmə sinfə rəngləri ayırmağı öyrətmək lazımdır. Bunu etmək üçün iki üsuldan istifadə edə bilərsiniz. Ancaq biri simmetriya qaydasını , ikincisi isə keçidi pozacaq .
// Первый способ, нарушая симметричность
// Метод переопределен в классе ColorPoint
@Override
public boolean equals(Object o) {
if (!(o instanceof ColorPoint)) return false;
return super.equals(o) && ((ColorPoint) o).color == color;
}
Bu halda, zəng point.equals(colorPoint)
dəyəri qaytaracaq true
və müqayisə colorPoint.equals(point)
qayıdacaq false
, çünki “öz” sinfinin obyektini gözləyir. Beləliklə, simmetriya qaydası pozulur. İkinci üsul, nöqtənin rəngi haqqında heç bir məlumat olmadığı halda "kor" yoxlama aparmağı əhatə edir, yəni sinifimiz var Point
. Və ya rəng haqqında məlumat varsa onu yoxlayın, yəni sinif obyektini müqayisə edin 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;
}
Burada keçid prinsipi aşağıdakı kimi pozulur. Tutaq ki, aşağıdakı obyektlərin tərifi var:
ColorPoint p1 = new ColorPoint(1, 2, Color.RED);
Point p2 = new Point(1, 2);
ColorPoint p3 = new ColorPoint(1, 2, Color.BLUE);
Beləliklə bərabərlik təmin edilsə də p1.equals(p2)
, p2.equals(p3)
dəyərini p1.equals(p3)
qaytaracaq false
. Eyni zamanda, ikinci üsul, mənim fikrimcə, daha az cəlbedici görünür, çünki Bəzi hallarda, alqoritm kor ola bilər və müqayisəni tam yerinə yetirməyə bilər və bu barədə məlumatınız olmaya bilər. Bir az poeziya Ümumiyyətlə, başa düşdüyüm kimi, bu problemin konkret həlli yoxdur. Kay Horstmann adlı bir nüfuzlu müəllifin rəyi var ki, operatorun istifadəsini obyektin sinfini qaytaran instanceof
metod çağırışı ilə əvəz edə bilərsiniz və obyektlərin özlərini müqayisə etməyə başlamazdan əvvəl onların eyni tipdə olduğundan əmin olun. getClass()
, və onların ümumi mənşəyi faktına diqqət yetirməyin. Beləliklə, simmetriya və keçid qaydaları təmin ediləcəkdir. Lakin eyni zamanda, barrikadanın o biri tərəfində geniş dairələrdə heç də az hörmətə malik olmayan başqa bir müəllif Coşua Blox dayanır, o hesab edir ki, bu yanaşma Barbara Liskovun əvəzetmə prinsipini pozur. Bu prinsipdə deyilir ki, “zəng kodu əsas sinfə onu bilmədən onun alt sinifləri ilə eyni şəkildə davranmalıdır . ” Və Horstmann tərəfindən təklif olunan həlldə bu prinsip açıq şəkildə pozulur, çünki bu, həyata keçirilməsindən asılıdır. Bir sözlə, məsələnin qaranlıq olduğu aydındır. Onu da qeyd etmək lazımdır ki, Horstmann öz yanaşmasının tətbiqi qaydasını aydınlaşdırır və sadə ingilis dilində yazır ki, dərsləri tərtib edərkən strategiyaya qərar verməlisən və əgər bərabərlik testi yalnız super sinif tərəfindən aparılacaqsa, siz bunu edə bilərsiniz. əməliyyat instanceof
. Əks halda, çekin semantikası törəmə sinifdən asılı olaraq dəyişdikdə və metodun həyata keçirilməsi iyerarxiyadan aşağı salınmalı olduqda, metoddan istifadə etməlisiniz getClass()
. Joshua Bloch, öz növbəsində, mirasdan imtina etməyi və sinifə ColorPoint
sinif daxil etməklə və xüsusi olaraq nöqtə haqqında məlumat əldə etmək üçün Point
giriş metodu təqdim etməklə obyekt kompozisiyasından istifadə etməyi təklif edir. asPoint()
Bu, bütün qaydaları pozmaqdan qaçınacaq, lakin mənim fikrimcə, kodu başa düşməyi çətinləşdirəcək. Üçüncü seçim, IDE-dən istifadə edərək bərabərlik metodunun avtomatik yaradılmasından istifadə etməkdir. İdeya, yeri gəlmişkən, Horstmann nəslini təkrarlayır, bir supersinifdə və ya onun nəsillərində bir metodun tətbiqi strategiyasını seçməyə imkan verir. Nəhayət, növbəti ardıcıllıq qaydası bildirir ki, obyektlər dəyişməsə belə x
, y
onları yenidən çağırmaq x.equals(y)
əvvəlki kimi eyni dəyəri qaytarmalıdır. Son qayda ondan ibarətdir ki, heç bir obyekt ilə bərabər olmamalıdır null
. Burada hər şey aydındır null
- bu qeyri-müəyyənlikdir, obyekt qeyri-müəyyənliyə bərabərdirmi? Aydın deyil, yəni false
.
Bərabərlərin müəyyən edilməsi üçün ümumi alqoritm
this
Obyekt istinadlarının və metod parametrlərinin bərabərliyini yoxlayıno
.if (this == o) return true;
- Bağlantının müəyyən edilib-edilmədiyini
o
, yəni olub-olmadığını yoxlayınnull
.
Gələcəkdə obyekt növlərini müqayisə edərkən operatordan istifadə ediləcəksə,instanceof
bu element atlana bilər, çünki bu parametrfalse
bu halda qaytarılırnull instanceof Object
. - Yuxarıdakı təsviri və öz intuisiyanızı rəhbər tutaraq operator və ya metoddan
this
istifadə edərək obyekt növlərini müqayisə edin .o
instanceof
getClass()
- Metod
equals
alt sinifdə ləğv edilibsə, zəng etməyinizə əmin olunsuper.equals(o)
- Parametr növünü
o
tələb olunan sinfə çevirin. - Bütün əhəmiyyətli obyekt sahələrinin müqayisəsini həyata keçirin:
- ibtidai növlər üçün (
float
və istisna olmaqladouble
) operatordan istifadə etməklə==
- istinad sahələri üçün onların metodunu çağırmalısınız
equals
- massivlər üçün siklik iterasiya və ya metoddan istifadə edə bilərsiniz
Arrays.equals()
- növləri üçün
float
vədouble
müvafiq sarğı siniflərinin müqayisə üsullarından istifadə etmək lazımdırFloat.compare()
vəDouble.compare()
- ibtidai növlər üçün (
- Və nəhayət, üç suala cavab verin: həyata keçirilən metod simmetrikdirmi ? Keçidli ? Razılaşdım ? Digər iki prinsip ( refleksivlik və əminlik ) adətən avtomatik olaraq həyata keçirilir.
HashCode qaydalarını ləğv edir
Hash, müəyyən bir zamanda vəziyyətini təsvir edən bir obyektdən yaradılan bir nömrədir. Bu nömrə Java-da ilk növbədə kimi hash cədvəllərində istifadə olunurHashMap
. Bu halda, obyekt əsasında ədədin alınmasının hash funksiyası elə həyata keçirilməlidir ki, elementlərin hash cədvəli üzrə nisbətən bərabər paylanmasını təmin etsin. Həm də funksiya müxtəlif düymələr üçün eyni dəyəri qaytardıqda toqquşma ehtimalını minimuma endirmək üçün.
Müqavilə hashCode
Hash funksiyasını həyata keçirmək üçün dil spesifikasiyası aşağıdakı qaydaları müəyyən edir:- metodun
hashCode
eyni obyektdə bir və ya bir neçə dəfə çağırılması, dəyərin hesablanmasında iştirak edən obyektin sahələrinin dəyişməməsi şərti ilə eyni hash dəyərini qaytarmalıdır. - iki obyektdə metodun çağırılması,
hashCode
əgər obyektlər bərabərdirsə, həmişə eyni nömrəni qaytarmalıdır (equals
bu obyektlərdə metodun çağırılması qaytarırtrue
). - iki qeyri-bərabər obyektdə metodun çağırılması
hashCode
müxtəlif hash dəyərlərini qaytarmalıdır. Bu tələb məcburi olmasa da, onun həyata keçirilməsinin hash cədvəllərinin işinə müsbət təsir edəcəyini nəzərə almaq lazımdır.
Bərabər və hashCode üsulları birlikdə ləğv edilməlidir
Yuxarıda təsvir edilən müqavilələrə əsasən belə çıxır ki, kodunuzda metodu ləğv edərkənequals
siz həmişə metodu ləğv etməlisiniz hashCode
. Əslində bir sinifin iki nümunəsi fərqli yaddaş sahələrində olduqları üçün fərqli olduğundan, bəzi məntiqi meyarlara görə müqayisə edilməlidir. Müvafiq olaraq, iki məntiqi ekvivalent obyekt eyni hash dəyərini qaytarmalıdır. Bu üsullardan yalnız biri ləğv edilərsə nə olar?
-
equals
HəhashCode
YoxDeyək ki, biz
equals
sinfimizdə bir metodu düzgün müəyyən etdik vəhashCode
metodu sinifdə olduğu kimi buraxmağa qərar verdikObject
. O zaman metod nöqteyi-nəzərindənequals
iki obyekt məntiqi olaraq bərabər olacaq, metod baxımından isəhashCode
onların heç bir ortaqlığı olmayacaq. Beləliklə, bir obyekti hash cədvəlinə yerləşdirməklə, biz onu açarla geri almamaq riskini daşıyırıq.
Məsələn, bu kimi:Map<Point, String> m = new HashMap<>(); m.put(new Point(1, 1), “Point A”); // pointName == null String pointName = m.get(new Point(1, 1));
Aydındır ki, yerləşdirilən obyekt və axtarılan obyekt məntiqi cəhətdən bərabər olsalar da, iki fərqli obyektdir. Amma, çünki müqaviləni pozduğumuz üçün onların fərqli hash dəyərləri var, deyə bilərik ki, hash cədvəlinin bağırsaqlarında bir yerdə obyektimizi itirmişik.
-
hashCode
Həequals
Yox.Əgər metodu ləğv etsək
hashCode
vəequals
metodun həyata keçirilməsini sinifdən miras alsaq nə olarObject
. Bildiyiniz kimi,equals
standart metod sadəcə göstəriciləri obyektlərlə müqayisə edir, onların eyni obyektə istinad edib-etmədiyini müəyyən edir. Fərz edək ki,hashCode
metodu bütün qanunlara uyğun olaraq yazdıq, yəni onu IDE-dən istifadə edərək yaratdıq və məntiqi olaraq eyni obyektlər üçün eyni hash dəyərlərini qaytaracaq. Aydındır ki, bununla biz artıq iki obyekti müqayisə etmək üçün hansısa mexanizmi müəyyən etmişik.Buna görə də, əvvəlki paraqrafdakı nümunə nəzəri olaraq həyata keçirilməlidir. Lakin biz hələ də hash cədvəlində obyektimizi tapa bilməyəcəyik. Baxmayaraq ki, biz buna yaxın olacağıq, çünki ən azı obyektin yatacağı bir hash masa səbəti tapacağıq.
Hash cədvəlində obyekti uğurla axtarmaq üçün açarın hash dəyərlərinin müqayisəsi ilə yanaşı, axtarış edilən obyektlə açarın məntiqi bərabərliyinin müəyyən edilməsi də istifadə olunur. Yəni
equals
metodu ləğv etmədən heç bir yol yoxdur.
HashCode-u təyin etmək üçün ümumi alqoritm
Budur, mənə elə gəlir ki, çox narahat olmamalı və sevimli IDE-də metod yaratmalısınız. Çünki bütün bu bitlərin qızıl nisbət axtarışında sağa və sola sürüşməsi, yəni normal paylama - bu tamamilə inadkar dostlar üçündür. Şəxsən mən eyni İdeyadan daha yaxşı və daha sürətli edə biləcəyimə şübhə edirəm.Nəticə əvəzinə
Beləliklə, metodların Java dilində dəqiq müəyyən edilmiş rolequals
oynadığını və iki obyektin məntiqi bərabərlik xarakteristikasını əldə etmək üçün nəzərdə tutulduğunu görürük . hashCode
Metod vəziyyətində, equals
bunun obyektlərin müqayisəsi ilə birbaşa əlaqəsi var, dolayı halda hashCode
, lazım olduqda, tutaq ki, hash cədvəllərində və ya oxşar məlumat strukturlarında obyektin təxmini yerini müəyyən etmək üçün obyektin axtarış sürətini artırmaq. Müqavilələrə əlavə olaraq , obyektlərin müqayisəsi ilə bağlı başqa bir tələb də var equals
. Bu interfeys metodunun a ilə hashCode
uyğunluğudur . Bu tələb tərtibatçını həmişə geri qayıtmağa məcbur edir . Yəni görürük ki, iki obyektin məntiqi müqayisəsi tətbiqin heç bir yerində ziddiyyət təşkil etməməli və həmişə ardıcıl olmalıdır. compareTo
Comparable
equals
x.equals(y) == true
x.compareTo(y) == 0
GO TO FULL VERSION