JavaRush /Java Blogu /Random-AZ /bərabərdir və hashCode üsulları: istifadə təcrübəsi

bərabərdir və hashCode üsulları: istifadə təcrübəsi

Qrupda dərc edilmişdir
Salam! Bu gün Java-da iki mühüm metoddan danışacağıq - equals()hashCode(). Bu, onlarla ilk dəfə deyil ki, tanış oluruq: JavaRush kursunun əvvəlində bu barədə qısa mühazirəequals() var idi - əgər onu unutmusunuzsa və ya əvvəllər görməmisinizsə, oxuyun. Metodlar bərabərdir &  hashCode: istifadə təcrübəsi - 1Bugünkü dərsdə bu anlayışlar haqqında ətraflı danışacağıq - inanın, danışacaq çox şey var! Və yeni bir şeyə keçməzdən əvvəl, artıq qeyd etdiklərimizlə bağlı yaddaşımızı təzələyək :) Yadınızdadır ki, “ ==” operatorundan istifadə etməklə iki obyektin adi müqayisəsi pis fikirdir, çünki “ ==” istinadları müqayisə edir. Bu yaxınlarda keçirilən mühazirədən avtomobillərlə nümunəmiz:
public class Car {

   String model;
   int maxSpeed;

   public static void main(String[] args) {

       Car car1 = new Car();
       car1.model = "Ferrari";
       car1.maxSpeed = 300;

       Car car2 = new Car();
       car2.model = "Ferrari";
       car2.maxSpeed = 300;

       System.out.println(car1 == car2);
   }
}
Konsol çıxışı:

false
Belə görünür ki, biz sinifin iki eyni obyektini yaratmışıq Car: iki maşındakı bütün sahələr eynidir, lakin müqayisənin nəticəsi hələ də yanlışdır. Səbəbini artıq bilirik: bağlantılar car1car2yaddaşdakı müxtəlif ünvanlara işarə edir, buna görə də onlar bərabər deyil. Biz hələ də iki istinadı deyil, iki obyekti müqayisə etmək istəyirik. Obyektləri müqayisə etmək üçün ən yaxşı həll equals().

bərabər() metodu

Xatırlaya bilərsiniz ki, biz bu metodu sıfırdan yaratmırıq, əksinə onu ləğv edirik - axı, metod equals()sinifdə müəyyən edilir Object. Ancaq adi formada az istifadə olunur:
public boolean equals(Object obj) {
   return (this == obj);
}
equals()Metod sinifdə belə müəyyən edilir Object. Bağlantıların eyni müqayisəsi. Onu niyə belə yaradıblar? Yaxşı, dilin yaradıcıları proqramınızda hansı obyektlərin bərabər hesab edildiyini, hansının bərabər olmadığını haradan bilirlər? :) Metodun əsas ideyası budur equals()- sinfin yaradıcısı bu sinfin obyektlərinin bərabərliyinin yoxlanıldığı xüsusiyyətləri müəyyən edir. Bunu etməklə siz equals()sinifinizdəki metodu ləğv etmiş olursunuz. “Xüsusiyyətləri özünüz müəyyənləşdirirsiniz” ifadəsinin mənasını tam başa düşmürsünüzsə, bir nümunəyə baxaq. Budur sadə bir insan sinfi - Man.
public class Man {

   private String noseSize;
   private String eyesColor;
   private String haircut;
   private boolean scars;
   private int dnaCode;

public Man(String noseSize, String eyesColor, String haircut, boolean scars, int dnaCode) {
   this.noseSize = noseSize;
   this.eyesColor = eyesColor;
   this.haircut = haircut;
   this.scars = scars;
   this.dnaCode = dnaCode;
}

   //getters, setters, etc.
}
Deyək ki, iki insanın əkiz, yoxsa sadəcə doppelgängers tərəfindən qohum olduğunu müəyyən etmək üçün bir proqram yazırıq. Beş xüsusiyyətimiz var: burun ölçüsü, göz rəngi, saç düzümü, çapıqların olması və DNT bioloji testinin nəticələri (sadəlik üçün - kod nömrəsi şəklində). Sizcə, bu xüsusiyyətlərdən hansı proqramımız əkiz qohumları müəyyən etməyə imkan verəcək? Metodlar bərabərdir &  hashCode: istifadə təcrübəsi - 2Təbii ki, yalnız bioloji test zəmanət verə bilər. İki insanın göz rəngi, saç düzümü, burnu, hətta çapıqları eyni ola bilər - dünyada çoxlu insan var və təsadüflərdən qaçmaq mümkün deyil. Bizə etibarlı mexanizm lazımdır: yalnız DNT testinin nəticəsi dəqiq nəticə çıxarmağa imkan verir. Bu, bizim metodumuz üçün nə deməkdir equals()? ManProqramımızın tələblərini nəzərə alaraq onu bir sinifdə yenidən təyin etməliyik . Metod iki obyektin sahəsini müqayisə etməlidir int dnaCodevə əgər onlar bərabərdirsə, o zaman obyektlər bərabərdir.
@Override
public boolean equals(Object o) {
   Man man = (Man) o;
   return dnaCode == man.dnaCode;
}
Həqiqətənmi bu qədər sadədir? Həqiqətən yox. Nəyisə qaçırdıq. Bu halda, biz obyektlərimiz üçün onların bərabərliyini təmin edən yalnız bir "əhəmiyyətli" sahəni təyin etdik - dnaCode. İndi təsəvvür edin ki, bizim 1 yox, 50 belə “əhəmiyyətli” sahəmiz olardı.Və əgər iki obyektin bütün 50 sahəsi bərabərdirsə, deməli obyektlər bərabərdir. Bu da baş verə bilər. Əsas problem ondan ibarətdir ki, 50 sahənin bərabərliyinin hesablanması çox vaxt aparan və resurs tələb edən prosesdir. İndi təsəvvür edin ki, sinifə əlavə olaraq, - ilə eyni sahələrə malik Manbir sinifimiz var . Başqa bir proqramçı sizin dərslərinizdən istifadə edərsə, o, proqramında asanlıqla belə bir şey yaza bilər: WomanMan
public static void main(String[] args) {

   Man man = new Man(........); //a bunch of parameters in the constructor

   Woman woman = new Woman(.........);//same bunch of parameters.

   System.out.println(man.equals(woman));
}
Bu halda, sahə qiymətlərini yoxlamağın mənası yoxdur: biz iki müxtəlif sinif obyektlərinə baxdığımızı görürük və onlar prinsipcə bərabər ola bilməzlər! equals()Bu o deməkdir ki , iki eyni sinif obyektlərinin müqayisəsi metoduna bir çek qoymalıyıq . Yaxşı ki, bu haqda fikirləşmişik!
@Override
public boolean equals(Object o) {
   if (getClass() != o.getClass()) return false;
   Man man = (Man) o;
   return dnaCode == man.dnaCode;
}
Amma bəlkə başqa bir şeyi unutmuşuq? Hmm... Ən azı obyekti özü ilə müqayisə etmədiyimizi yoxlamaq lazımdır! Əgər A və B istinadları yaddaşda eyni ünvana işarə edirsə, deməli, onlar eyni obyektdir və 50 sahəni müqayisə etməyə vaxt itirməyə ehtiyacımız yoxdur.
@Override
public boolean equals(Object o) {
   if (this == o) return true;
   if (getClass() != o.getClass()) return false;
   Man man = (Man) o;
   return dnaCode == man.dnaCode;
}
Bundan əlavə, üçün bir çek əlavə etmək zərər verməzdi null: heç bir obyekt ilə bərabər ola bilməz null, bu halda əlavə çeklərdə heç bir nöqtə yoxdur. Bütün bunları nəzərə alsaq, equals()sinif metodumuz Manbelə görünəcək:
@Override
public boolean equals(Object o) {
   if (this == o) return true;
   if (o == null || getClass() != o.getClass()) return false;
   Man man = (Man) o;
   return dnaCode == man.dnaCode;
}
Yuxarıda göstərilən bütün ilkin yoxlamaları həyata keçiririk. Əgər belə çıxsa:
  • eyni sinifin iki obyektini müqayisə edirik
  • bu eyni obyekt deyil
  • obyektimizi müqayisə etmiriknull
...sonra biz əhəmiyyətli xüsusiyyətlərin müqayisəsinə keçirik. Bizim vəziyyətimizdə dnaCodeiki obyektin sahələri. Bir metodu ləğv edərkən equals(), bu tələblərə əməl etdiyinizə əmin olun:
  1. Refleksivlik.

    equals()İstənilən obyekt öz-özünə olmalıdır .
    Biz artıq bu tələbi nəzərə almışıq. Bizim metodumuz deyir:

    if (this == o) return true;

  2. Simmetriya.

    Əgər a.equals(b) == true, o zaman b.equals(a)geri qayıtmalıdır true.
    Bizim metodumuz da bu tələbə cavab verir.

  3. Keçidlilik.

    Əgər iki cisim hansısa üçüncü obyektə bərabərdirsə, onda onlar bir-birinə bərabər olmalıdırlar.
    Əgər a.equals(b) == truea.equals(c) == truevarsa, çek b.equals(c)də doğru qaytarmalıdır.

  4. Davamlılıq.

    İşin nəticələri equals()yalnız ona daxil olan sahələr dəyişdikdə dəyişməlidir. İki obyektin məlumatları dəyişməyibsə, yoxlamanın nəticələri equals()həmişə eyni olmalıdır.

  5. ilə bərabərsizlik null.

    İstənilən obyekt üçün çek a.equals(null)false qaytarmalıdır.Bu,
    sadəcə bəzi “faydalı tövsiyələr” toplusu deyil, Oracle sənədlərində nəzərdə tutulmuş ciddi metodlar müqaviləsidir.

hashCode() metodu

İndi üsul haqqında danışaq hashCode(). Niyə lazımdır? Məhz eyni məqsəd üçün - obyektlərin müqayisəsi. Amma bizdə artıq var equals()! Niyə başqa üsul? Cavab sadədir: məhsuldarlığı artırmaq. Java-da , metodu ilə təmsil olunan hash funksiyası hashCode()hər hansı obyekt üçün sabit uzunluqlu rəqəmli dəyər qaytarır. Java vəziyyətində, metod hashCode()32 bitlik bir növ qaytarır int. İki ədədi bir-biri ilə müqayisə etmək metoddan istifadə edərək iki obyekti müqayisə etməkdən daha sürətli olur equals(), xüsusən də çoxlu sahələr istifadə edirsə. Proqramımız obyektləri müqayisə edəcəksə, bunu hash kodu ilə etmək daha asandır və yalnız onlar bərabər olduqda hashCode()- ilə müqayisəyə davam edin equals(). Yeri gəlmişkən, bu, hash-əsaslı məlumat strukturlarının necə işlədiyidir - məsələn, sizin bildiyiniz HashMap! Metod hashCode(), eynilə kimi equals(), tərtibatçının özü tərəfindən ləğv edilir. Eynilə üçün olduğu kimi equals(), metodun hashCode()Oracle sənədlərində göstərilən rəsmi tələbləri var:
  1. Əgər iki obyekt bərabərdirsə (yəni metod equals()doğru qaytarırsa), onlar eyni hash koduna malik olmalıdırlar.

    Əks halda bizim üsullarımız mənasız olacaq. Yoxlama hashCode(), dediyimiz kimi, performansı artırmaq üçün ilk növbədə olmalıdır. Əgər hash kodları fərqlidirsə, obyektlərin əslində bərabər olmasına baxmayaraq, çek false qaytaracaq (metodda müəyyən etdiyimiz kimi equals()).

  2. Metod hashCode()eyni obyektdə bir neçə dəfə çağırılırsa, o, hər dəfə eyni nömrəni qaytarmalıdır.

  3. 1-ci qayda tərsinə işləmir. İki fərqli obyekt eyni hash koduna malik ola bilər.

Üçüncü qayda bir az qarışıqdır. Bu necə ola bilər? İzahat olduqca sadədir. Metod hashCode()qaytarır int. int32 bitlik nömrədir. Onun məhdud sayda dəyərləri var - -2,147,483,648-dən +2,147,483,647-ə qədər. Başqa sözlə, rəqəmin 4 milyarddan bir qədər çox dəyişməsi var int. İndi təsəvvür edin ki, siz Yer üzündə yaşayan bütün insanlar haqqında məlumatların saxlanması üçün proqram yaradırsınız. Hər bir insanın öz sinif obyekti olacaq Man. Yer üzündə ~7,5 milyard insan yaşayır. Başqa sözlə, obyektləri rəqəmlərə çevirmək üçün nə qədər yaxşı bir alqoritm Manyazsaq da, sadəcə olaraq kifayət qədər nömrəmiz olmayacaq. Cəmi 4,5 milyard seçimimiz və daha çox insanımız var. Bu o deməkdir ki, nə qədər çalışsaq da, hash kodları bəzi fərqli insanlar üçün eyni olacaq. Bu vəziyyət (iki fərqli obyektin hash kodları uyğun gəlir) toqquşma adlanır. Bir metodu ləğv edərkən proqramçının məqsədlərindən biri hashCode()potensial toqquşma sayını mümkün qədər azaltmaqdır. Bütün bu qaydaları nəzərə alaraq, hashCode()sinif üçün metodumuz necə olacaq ? ManBunun kimi:
@Override
public int hashCode() {
   return dnaCode;
}
Təəccübləndiniz? :) Gözlənilmədən, amma tələblərə baxsanız, hər şeyə əməl etdiyimizi görərsiniz. Bizimkilərin equals()doğru qaytardığı obyektlər -də bərabər olacaq hashCode(). Əgər iki obyektimiz Mandəyər baxımından bərabərdirsə equals(yəni eyni dəyərə malikdirlər dnaCode), metodumuz eyni ədədi qaytaracaq. Daha mürəkkəb bir nümunəyə baxaq. Tutaq ki, proqramımız kolleksiyaçı müştərilər üçün lüks avtomobilləri seçməlidir. Kolleksiya mürəkkəb bir işdir və onun bir çox xüsusiyyətləri var. 1963-cü ilin avtomobili 1964-cü ildəki eyni avtomobildən 100 dəfə baha ola bilər. 1970-ci ilin qırmızı avtomobili eyni ilin eyni markalı mavi avtomobilindən 100 dəfə baha ola bilər. Metodlar bərabərdir &  hashCode: istifadə təcrübəsi - 4Birinci halda, siniflə Manbiz sahələrin əksəriyyətini (yəni, şəxsiyyət xüsusiyyətlərini) əhəmiyyətsiz kimi atdıq və müqayisə üçün yalnız sahəni istifadə etdik dnaCode. Burada çox unikal bir sahə ilə işləyirik və kiçik detallar ola bilməz! Budur bizim sinif LuxuryAuto:
public class LuxuryAuto {

   private String model;
   private int manufactureYear;
   private int dollarPrice;

   public LuxuryAuto(String model, int manufactureYear, int dollarPrice) {
       this.model = model;
       this.manufactureYear = manufactureYear;
       this.dollarPrice = dollarPrice;
   }

   //... getters, setters, etc.
}
Burada müqayisə apararkən bütün sahələri nəzərə almalıyıq. Hər hansı bir səhv müştəri üçün yüz minlərlə dollara başa gələ bilər, ona görə də təhlükəsiz olmaq daha yaxşıdır:
@Override
public boolean equals(Object o) {
   if (this == o) return true;
   if (o == null || getClass() != o.getClass()) return false;

   LuxuryAuto that = (LuxuryAuto) o;

   if (manufactureYear != that.manufactureYear) return false;
   if (dollarPrice != that.dollarPrice) return false;
   return model.equals(that.model);
}
Metodumuzda equals()əvvəllər haqqında danışdığımız bütün yoxlamaları unutmadıq. Amma indi biz obyektlərimizin üç sahəsinin hər birini müqayisə edirik. Bu proqramda bərabərlik mütləq, hər sahədə olmalıdır. Nə haqqında hashCode?
@Override
public int hashCode() {
   int result = model == null ? 0 : model.hashCode();
   result = result + manufactureYear;
   result = result + dollarPrice;
   return result;
}
modelSinifimizdəki sahə simlidir. Bu rahatdır: Stringüsul hashCode()artıq sinifdə ləğv edilib. Sahənin hash kodunu hesablayırıq modelvə ona digər iki ədədi sahənin cəmini əlavə edirik. Java-da toqquşmaların sayını azaltmaq üçün istifadə edilən kiçik bir hiylə var: hash kodunu hesablayarkən, aralıq nəticəni tək əsas ədədə vurun. Ən çox istifadə olunan rəqəm 29 və ya 31-dir. Biz indi riyaziyyatın təfərrüatlarına girməyəcəyik, lakin gələcəkdə istinad üçün unutmayın ki, aralıq nəticələri kifayət qədər böyük tək ədədə vurmaq hashın nəticələrini “yaymağa” kömək edir. funksiyasını yerinə yetirir və eyni hashcode ilə daha az obyektlə nəticələnir. LuxuryAuto-dakı metodumuz üçün hashCode()bu belə görünəcək:
@Override
public int hashCode() {
   int result = model == null ? 0 : model.hashCode();
   result = 31 * result + manufactureYear;
   result = 31 * result + dollarPrice;
   return result;
}
Bu mexanizmin bütün incəlikləri haqqında daha çox StackOverflow-dakı bu yazıda , həmçinin Joshua Blochun " Effektiv Java " kitabında oxuya bilərsiniz . Nəhayət, qeyd etməyə dəyər daha bir vacib məqam var. equals()Hər dəfə ləğv edərkən hashCode()biz bu üsullarda nəzərə alınan obyektin müəyyən sahələrini seçdik. Amma müxtəlif sahələri nəzərə ala bilərikmi equals()hashCode()? Texniki cəhətdən edə bilərik. Ancaq bu pis fikirdir və bunun səbəbi:
@Override
public boolean equals(Object o) {
   if (this == o) return true;
   if (o == null || getClass() != o.getClass()) return false;

   LuxuryAuto that = (LuxuryAuto) o;

   if (manufactureYear != that.manufactureYear) return false;
   return dollarPrice == that.dollarPrice;
}

@Override
public int hashCode() {
   int result = model == null ? 0 : model.hashCode();
   result = 31 * result + manufactureYear;
   result = 31 * result + dollarPrice;
   return result;
}
Budur LuxuryAuto sinfi equals()üçün üsullarımız . hashCode()Metod hashCode()dəyişməz qaldı və equals()biz sahəni metoddan çıxardıq model. İndi model iki obyektin müqayisəsi üçün xarakterik deyil equals(). Amma hash kodunu hesablayarkən yenə də nəzərə alınır. Nəticədə nə əldə edəcəyik? Gəlin iki avtomobil yaradaq və yoxlayaq!
public class Main {

   public static void main(String[] args) {

       LuxuryAuto ferrariGTO = new LuxuryAuto("Ferrari 250 GTO", 1963, 70000000);
       LuxuryAuto ferrariSpider = new LuxuryAuto("Ferrari 335 S Spider Scaglietti", 1963, 70000000);

       System.out.println("Are these two objects equal to each other?");
       System.out.println(ferrariGTO.equals(ferrariSpider));

       System.out.println("What are their hash codes?");
       System.out.println(ferrariGTO.hashCode());
       System.out.println(ferrariSpider.hashCode());
   }
}

Эти два an object равны друг другу?
true
Какие у них хэш-codeы?
-1372326051
1668702472
Xəta! Onlar üçün müxtəlif sahələrdən istifadə etməklə equals()hashCode()biz onlar üçün yaradılmış müqaviləni pozmuşuq! İki bərabər equals()obyekt eyni hash koduna malik olmalıdır. Onlar üçün fərqli mənalar aldıq. Bu cür səhvlər ən inanılmaz nəticələrə səbəb ola bilər, xüsusən də hashlərdən istifadə edən kolleksiyalarla işləyərkən. Buna görə də, yenidən təyin edərkən equals()hashCode()eyni sahələri istifadə etmək düzgün olacaq. Mühazirə olduqca uzun oldu, amma bu gün siz çoxlu yeni şeylər öyrəndiniz! :) Problemlərin həllinə qayıtmağın vaxtıdır!
Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION