JavaRush /Java Blogu /Random-AZ /Bərabər və hashCode müqavilələri və ya nə olursa olsun
Aleksandr Zimin
Səviyyə
Санкт-Петербург

Bərabər və hashCode müqavilələri və ya nə olursa olsun

Qrupda dərc edilmişdir
Java proqramçılarının böyük əksəriyyəti, əlbəttə ki, metodların bir-biri ilə sıx əlaqəli olduğunu bilir equalshashCodebu 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 Thread. Onlar üçün equalssinfin təqdim etdiyi metodun həyata keçirilməsi Objectkifayət qədərdir. Başqa bir misal enum sinifləridir ( Enum).
  • Ə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, java.util.Randomeyni 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.
  • Genişləndirdiyiniz sinif artıq metodun öz tətbiqinə malik olduqda equalsvə bu tətbiqin davranışı sizə uyğun gəlir.
  • Məsələn, , sinifləri üçün həyata Setkeçirilməsi müvafiq olaraq , və içərisindədir . ListMapequalsAbstractSetAbstractListAbstractMap
  • equalsVə nəhayət, sinifinizin əhatə dairəsi privatevə ya nə vaxt olduğunu ləğv etməyə ehtiyac yoxdur package-privatevə bu metodun heç vaxt çağırılmayacağına əminsiniz.

müqaviləyə bərabərdir

Bir metodu ləğv edərkən, equalstərtibatçı Java dilinin spesifikasiyasında müəyyən edilmiş əsas qaydalara riayət etməlidir.
  • Refleksivlik
  • hər hansı verilmiş dəyər üçün xifadə x.equals(x)qayıtmalıdır true.
    Verilmiş - belə mənadax != null
  • Simmetriya
  • hər hansı verilmiş dəyər üçün xy, yalnız qayıtdıqda x.equals(y)qayıtmalıdır . truey.equals(x)true
  • Keçidlilik
  • hər hansı verilmiş dəyərlər üçün xyəgər zqaytarırsa və qaytarırsa x.equals(y), dəyəri qaytarmalıdır . truey.equals(z)truex.equals(z)true
  • Ardıcıllıq
  • hər hansı verilmiş qiymətlər üçün xytə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.
  • Müqayisə null
  • hər hansı bir dəyər üçün xzə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ır equals(), 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. equalsMə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, Pointkoordinatları olan bir sinifə xyonu genişləndirərək nöqtənin rəngini əlavə etməlisiniz. ColorPointBunu etmək üçün müvafiq sahə ilə bir sinif elan etməlisiniz color. Beləliklə, əgər genişləndirilmiş sinifdə biz equalsana metodu çağırırıqsa və valideyndə yalnız koordinatların xvə 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 truevə 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 instanceofmetod ç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ə, simmetriyakeç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ə ColorPointsinif daxil etməklə və xüsusi olaraq nöqtə haqqında məlumat əldə etmək üçün Pointgiriş 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, yonları 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

  1. thisObyekt istinadlarının və metod parametrlərinin bərabərliyini yoxlayın o.
    if (this == o) return true;
  2. Bağlantının müəyyən edilib-edilmədiyini o, yəni olub-olmadığını yoxlayın null.
    Gələcəkdə obyekt növlərini müqayisə edərkən operatordan istifadə ediləcəksə, instanceofbu element atlana bilər, çünki bu parametr falsebu halda qaytarılır null instanceof Object.
  3. Yuxarıdakı təsviri və öz intuisiyanızı rəhbər tutaraq operator və ya metoddan thisistifadə edərək obyekt növlərini müqayisə edin .oinstanceofgetClass()
  4. Metod equalsalt sinifdə ləğv edilibsə, zəng etməyinizə əmin olunsuper.equals(o)
  5. Parametr növünü otələb olunan sinfə çevirin.
  6. Bütün əhəmiyyətli obyekt sahələrinin müqayisəsini həyata keçirin:
    • ibtidai növlər üçün ( floatvə istisna olmaqla double) operatordan istifadə etməklə==
    • istinad sahələri üçün onların metodunu çağırmalısınızequals
    • massivlər üçün siklik iterasiya və ya metoddan istifadə edə bilərsinizArrays.equals()
    • növləri üçün floatdoublemüvafiq sarğı siniflərinin müqayisə üsullarından istifadə etmək lazımdır Float.compare()Double.compare()
  7. 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ə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ə olunur HashMap. 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 hashCodeeyni 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 ( equalsbu obyektlərdə metodun çağırılması qaytarır true).
  • iki qeyri-bərabər obyektdə metodun çağırılması hashCodemü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ən equalssiz 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?
  1. equalshashCodeYox

    Deyək ki, biz equalssinfimizdə bir metodu düzgün müəyyən etdik və hashCodemetodu sinifdə olduğu kimi buraxmağa qərar verdik Object. O zaman metod nöqteyi-nəzərindən equalsiki obyekt məntiqi olaraq bərabər olacaq, metod baxımından isə hashCodeonları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.

  2. hashCodeequalsYox.

    Əgər metodu ləğv etsək hashCodeequalsmetodun həyata keçirilməsini sinifdən miras alsaq nə olar Object. Bildiyiniz kimi, equalsstandart 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, hashCodemetodu 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 equalsmetodu 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ş rol equalsoynadığını və iki obyektin məntiqi bərabərlik xarakteristikasını əldə etmək üçün nəzərdə tutulduğunu görürük . hashCodeMetod vəziyyətində, equalsbunun 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ə hashCodeuyğ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. compareToComparableequalsx.equals(y) == truex.compareTo(y) == 0

Mənbələr

Effektiv Java, İkinci Nəşr. Joshua Bloch. Çox yaxşı kitabın pulsuz tərcüməsi. Java, peşəkar kitabxana. Cild 1. Əsaslar. Kay Horstmann. Bir az daha az nəzəriyyə və daha çox təcrübə. Ancaq hər şey Bloch kimi ətraflı təhlil edilmir. Baxmayaraq ki, eyni bərabər () ilə bağlı bir görünüş var. Şəkillərdə məlumat strukturları. HashMap Java-da HashMap cihazı haqqında son dərəcə faydalı məqalə. Mənbələrə baxmaq əvəzinə.
Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION