CodeGym /Java Blog /Random /equals at hashCode na mga pamamaraan: pinakamahusay na ka...
John Squirrels
Antas
San Francisco

equals at hashCode na mga pamamaraan: pinakamahusay na kasanayan

Nai-publish sa grupo
Hi! Ngayon ay pag-uusapan natin ang tungkol sa dalawang mahahalagang pamamaraan sa Java: equals()at hashCode(). Hindi ito ang unang pagkakataon na nakilala natin sila: ang kursong CodeGym ay nagsisimula sa isang maikling aralin tungkol sa equals()— basahin ito kung nakalimutan mo na ito o hindi mo pa nakita... equals at hashCode na mga pamamaraan: pinakamahusay na kagawian - 1Sa aralin ngayon, pag-uusapan natin ang tungkol sa ang mga konseptong ito nang detalyado. At maniwala ka sa akin, mayroon tayong pag-uusapan! Ngunit bago tayo lumipat sa bago, i-refresh natin ang napag-usapan na natin :) Tulad ng naaalala mo, kadalasan ay isang masamang ideya na ihambing ang dalawang bagay gamit ang ==operator, dahil ==inihahambing ang mga sanggunian. Narito ang aming halimbawa sa mga kotse mula sa isang kamakailang aralin:

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);
   }
}
Output ng console:

false
Mukhang nakagawa kami ng dalawang magkatulad Carna bagay: ang mga halaga ng mga katumbas na field ng dalawang bagay ng kotse ay pareho, ngunit ang resulta ng paghahambing ay mali pa rin. Alam na natin ang dahilan: ang car1at car2ang mga sanggunian ay tumuturo sa iba't ibang mga address ng memorya, kaya hindi sila pantay. Ngunit gusto pa rin naming ihambing ang dalawang bagay, hindi dalawang sanggunian. Ang pinakamahusay na solusyon para sa paghahambing ng mga bagay ay ang equals()pamamaraan.

katumbas ng() na pamamaraan

Maaari mong maalala na hindi namin nililikha ang paraang ito mula sa simula, sa halip ay i-override namin ito: ang equals()pamamaraan ay tinukoy sa Objectklase. Iyon ay sinabi, sa karaniwang anyo nito, ito ay walang gaanong pakinabang:

public boolean equals(Object obj) {
   return (this == obj);
}
Ito ay kung paano equals()tinukoy ang pamamaraan sa Objectklase. Ito ay isang paghahambing ng mga sanggunian muli. Bakit nila ginawa iyon? Well, paano malalaman ng mga tagalikha ng wika kung aling mga bagay sa iyong programa ang itinuturing na pantay at alin ang hindi? :) Ito ang pangunahing punto ng equals()pamamaraan — ang tagalikha ng isang klase ay ang siyang tumutukoy kung aling mga katangian ang ginagamit kapag sinusuri ang pagkakapantay-pantay ng mga bagay ng klase. Pagkatapos ay i-override mo ang equals()pamamaraan sa iyong klase. Kung hindi mo lubos na nauunawaan ang kahulugan ng "tinutukoy kung aling mga katangian", isaalang-alang natin ang isang halimbawa. Narito ang isang simpleng klase na kumakatawan sa isang lalaki: 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.
}
Ipagpalagay na nagsusulat kami ng isang programa na kailangang tukuyin kung ang dalawang tao ay magkaparehong kambal o mga kamukha lang. Mayroon kaming limang katangian: laki ng ilong, kulay ng mata, estilo ng buhok, pagkakaroon ng mga peklat, at mga resulta ng pagsusuri sa DNA (para sa pagiging simple, kinakatawan namin ito bilang isang integer code). Alin sa mga katangiang ito ang sa tingin mo ay magbibigay-daan sa aming programa na makilala ang magkatulad na kambal? equals at hashCode na mga pamamaraan: pinakamahusay na kagawian - 2Syempre, DNA test lang ang makakapagbigay ng garantiya. Ang dalawang tao ay maaaring magkaroon ng parehong kulay ng mata, gupit, ilong, at kahit na mga peklat — maraming tao sa mundo, at imposibleng magarantiya na walang doppelgängers doon. Ngunit kailangan natin ng maaasahang mekanismo: tanging ang resulta ng pagsusuri sa DNA ang magbibigay-daan sa atin na makagawa ng tumpak na konklusyon. Ano ang ibig sabihin nito para sa aming equals()pamamaraan? Kailangan nating i-override ito saManklase, na isinasaalang-alang ang mga kinakailangan ng aming programa. Dapat ihambing ng pamamaraan ang int dnaCodefield ng dalawang bagay. Kung sila ay pantay, kung gayon ang mga bagay ay pantay.

@Override
public boolean equals(Object o) {
   Man man = (Man) o;
   return dnaCode == man.dnaCode;
}
Ganun ba talaga kasimple? Hindi naman. May na-overlook kami. Para sa aming mga object, isang field lang ang natukoy namin na may kaugnayan sa pagtatatag ng pagkakapantay-pantay ng object: dnaCode. Ngayon isipin na wala kaming 1, ngunit 50 nauugnay na mga field. At kung ang lahat ng 50 patlang ng dalawang bagay ay pantay, kung gayon ang mga bagay ay pantay. Posible rin ang ganitong senaryo. Ang pangunahing problema ay ang pagtatatag ng pagkakapantay-pantay sa pamamagitan ng paghahambing ng 50 na mga patlang ay isang prosesong umuubos ng oras at masinsinang mapagkukunan. Ngayon isipin na bilang karagdagan sa aming Manklase, mayroon kaming isang Womanklase na may eksaktong parehong mga field na umiiral sa Man. Kung ang isa pang programmer ay gumagamit ng aming mga klase, madali siyang magsulat ng code na tulad nito:

public static void main(String[] args) {
  
   Man man = new Man(........); // A bunch of parameters in the constructor

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

   System.out.println(man.equals(woman));
}
Sa kasong ito, ang pagsuri sa mga halaga ng patlang ay walang kabuluhan: madali nating makita na mayroon tayong mga bagay ng dalawang magkaibang klase, kaya walang paraan na magkapantay sila! Nangangahulugan ito na dapat tayong magdagdag ng tseke sa equals()pamamaraan, paghahambing ng mga klase ng mga pinaghahambing na bagay. Buti naman naisip namin yun!

@Override
public boolean equals(Object o) {
   if (getClass() != o.getClass()) return false;
   Man man = (Man) o;
   return dnaCode == man.dnaCode;
}
Pero baka may iba na tayong nakalimutan? Hmm... Sa pinakamababa, dapat nating suriin na hindi natin inihahambing ang isang bagay sa sarili nito! Kung ang mga reference na A at B ay tumuturo sa parehong memory address, kung gayon ang mga ito ay iisang bagay, at hindi natin kailangang mag-aksaya ng oras at magkumpara ng 50 field.

@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;
}
Hindi rin masakit na magdagdag ng tseke para sa null: walang bagay na maaaring katumbas ng null. Kaya, kung ang parameter ng pamamaraan ay null, kung gayon walang punto sa mga karagdagang pagsusuri. Sa lahat ng ito sa isip, ang aming equals()pamamaraan para sa Manklase ay ganito ang hitsura:

@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;
}
Ginagawa namin ang lahat ng mga paunang pagsusuri na binanggit sa itaas. Sa pagtatapos ng araw, kung:
  • kami ay naghahambing ng dalawang bagay ng parehong klase
  • at ang pinaghahambing na mga bagay ay hindi ang parehong bagay
  • at ang naipasa na bagay ay hindinull
...pagkatapos ay magpatuloy tayo sa paghahambing ng mga nauugnay na katangian. Para sa amin, nangangahulugan ito ng dnaCodemga patlang ng dalawang bagay. Kapag na-override ang equals()pamamaraan, tiyaking sundin ang mga kinakailangang ito:
  1. Reflexivity.

    Kapag ginamit ang equals()pamamaraan upang ihambing ang anumang bagay sa sarili nito, dapat itong bumalik ng totoo.
    Nakasunod na kami sa kinakailangang ito. Kasama sa aming pamamaraan ang:

    
    if (this == o) return true;
    

  2. Simetrya.

    Kung a.equals(b) == true, pagkatapos ay b.equals(a)dapat bumalik true.
    Ang aming pamamaraan ay nakakatugon din sa pangangailangang ito.

  3. Transitivity.

    Kung ang dalawang bagay ay katumbas ng ilang pangatlong bagay, kung gayon dapat silang pantay sa isa't isa.
    Kung a.equals(b) == trueat a.equals(c) == true, pagkatapos b.equals(c)ay dapat ding bumalik ng totoo.

  4. Pagtitiyaga.

    Ang resulta ng equals()ay dapat magbago lamang kapag ang mga patlang na kasangkot ay nabago. Kung ang data ng dalawang bagay ay hindi nagbabago, ang resulta ng equals()ay dapat palaging pareho.

  5. Hindi pagkakapantay-pantay sa null.

    Para sa anumang bagay, a.equals(null)dapat magbalik ng mali
    .

hashCode() na pamamaraan

Ngayon pag-usapan natin ang hashCode()pamamaraan. Bakit kailangan? Para sa eksaktong parehong layunin - upang ihambing ang mga bagay. Ngunit mayroon na kami equals()! Bakit ibang paraan? Ang sagot ay simple: upang mapabuti ang pagganap. Ang isang hash function, na kinakatawan sa Java gamit ang hashCode()pamamaraan, ay nagbabalik ng fixed-length na numerical value para sa anumang bagay. Sa Java, ang hashCode()pamamaraan ay nagbabalik ng 32-bit na numero ( int) para sa anumang bagay. Ang paghahambing ng dalawang numero ay mas mabilis kaysa sa paghahambing ng dalawang bagay gamit ang equals()pamamaraan, lalo na kung ang pamamaraang iyon ay isinasaalang-alang ang maraming mga patlang. Kung ang aming programa ay naghahambing ng mga bagay, ito ay mas madaling gawin gamit ang isang hash code. Kung ang mga bagay ay pantay-pantay batay sa hashCode()pamamaraan, ang paghahambing ay magpapatuloy saequals()paraan. Sa pamamagitan ng paraan, ito ay kung paano gumagana ang hash-based na mga istruktura ng data, halimbawa, ang pamilyar na HashMap! Ang hashCode()pamamaraan, tulad ng equals()pamamaraan, ay na-override ng developer. At tulad ng equals(), ang hashCode()pamamaraan ay may mga opisyal na kinakailangan na nabaybay sa dokumentasyon ng Oracle:
  1. Kung ang dalawang bagay ay pantay (ibig sabihin, ang equals()pamamaraan ay nagbabalik ng totoo), dapat silang magkaroon ng parehong hash code.

    Kung hindi, ang aming mga pamamaraan ay magiging walang kabuluhan. Gaya ng nabanggit namin sa itaas, hashCode()dapat na mauna ang isang pagsusuri upang mapabuti ang pagganap. Kung ang mga hash code ay iba, ang tseke ay magbabalik ng mali, kahit na ang mga bagay ay aktwal na pantay ayon sa kung paano namin tinukoy ang equals()pamamaraan.

  2. Kung ang hashCode()pamamaraan ay tinawag nang maraming beses sa parehong bagay, dapat itong ibalik ang parehong numero sa bawat oras.

  3. Ang Panuntunan 1 ay hindi gumagana sa kabaligtaran na direksyon. Ang dalawang magkaibang bagay ay maaaring magkaroon ng parehong hash code.

Ang ikatlong panuntunan ay medyo nakakalito. Paanong nangyari to? Ang paliwanag ay medyo simple. Ang hashCode()pamamaraan ay nagbabalik ng isang int. Ang isang intay isang 32-bit na numero. Mayroon itong limitadong hanay ng mga halaga: mula -2,147,483,648 hanggang +2,147,483,647. Sa madaling salita, mayroon lamang mahigit 4 bilyong posibleng halaga para sa isang int. Ngayon isipin na gumagawa ka ng isang programa upang mag-imbak ng data tungkol sa lahat ng taong naninirahan sa Earth. Ang bawat tao ay tumutugma sa sarili nitong Personbagay (katulad ng Manklase). Mayroong ~7.5 bilyong tao ang naninirahan sa planeta. Sa madaling salita, gaano man katalino ang algorithm na isinusulat namin para sa pag-convertPersonobject sa isang int, wala kaming sapat na posibleng mga numero. Mayroon lamang tayong 4.5 bilyong posibleng int value, ngunit mas marami pa ang tao kaysa doon. Nangangahulugan ito na gaano man tayo kahirap, ang ilang iba't ibang tao ay magkakaroon ng parehong mga hash code. Kapag nangyari ito (nagtutugma ang mga hash code para sa dalawang magkaibang bagay) tinatawag namin itong banggaan. Kapag na-override ang hashCode()pamamaraan, ang isa sa mga layunin ng programmer ay upang mabawasan ang potensyal na bilang ng mga banggaan. Sa pagsasaalang-alang sa lahat ng mga panuntunang ito, ano ang magiging hashCode()hitsura ng pamamaraan sa Personklase? Ganito:

@Override
public int hashCode() {
   return dnaCode;
}
Nagulat? :) Kung titingnan mo ang mga kinakailangan, makikita mo na sumusunod kami sa lahat ng ito. Ang mga bagay na kung saan ang aming equals()pamamaraan ay nagbabalik ng totoo ay magiging katumbas din ayon sa hashCode(). Kung ang aming dalawang Personbagay ay pantay-pantay sa equals(iyon ay, mayroon silang pareho dnaCode), kung gayon ang aming pamamaraan ay nagbabalik ng parehong numero. Isaalang-alang natin ang isang mas mahirap na halimbawa. Ipagpalagay na ang aming programa ay dapat pumili ng mga mamahaling kotse para sa mga kolektor ng kotse. Ang pagkolekta ay maaaring maging isang kumplikadong libangan na may maraming mga kakaiba. Ang isang partikular na 1963 na kotse ay maaaring nagkakahalaga ng 100 beses na mas mataas kaysa sa isang 1964 na kotse. Ang isang 1970 na pulang kotse ay maaaring nagkakahalaga ng 100 beses na mas mataas kaysa sa isang asul na kotse ng parehong tatak ng parehong taon. equals at hashCode na mga pamamaraan: pinakamahusay na kagawian - 4Sa aming nakaraang halimbawa, kasama ang Personklase, itinapon namin ang karamihan sa mga patlang (ibig sabihin, mga katangian ng tao) bilang hindi gaanong mahalaga at ginamit lamang angdnaCodelarangan sa paghahambing. Nagtatrabaho na kami ngayon sa isang napaka-idiosyncratic na kaharian, kung saan walang mga hindi gaanong mahalagang detalye! Narito ang aming LuxuryAutoklase:

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.
}
Ngayon ay dapat nating isaalang-alang ang lahat ng mga larangan sa ating mga paghahambing. Ang anumang pagkakamali ay maaaring magastos ng isang kliyente ng daan-daang libong dolyar, kaya mas mabuting maging sobrang ligtas:

@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);
}
Sa aming equals()pamamaraan, hindi namin nakalimutan ang lahat ng mga tseke na napag-usapan namin kanina. Ngunit ngayon inihambing namin ang bawat isa sa tatlong mga patlang ng aming mga bagay. Para sa programang ito, kailangan natin ng ganap na pagkakapantay-pantay, ibig sabihin, pagkakapantay-pantay ng bawat larangan. Paano naman hashCode?

@Override
public int hashCode() {
   int result = model == null ? 0 : model.hashCode();
   result = result + manufactureYear;
   result = result + dollarPrice;
   return result;
}
Ang modelfield sa aming klase ay isang String. Ito ay maginhawa, dahil Stringna-override na ng klase ang hashCode()pamamaraan. Kinakalkula namin ang modelhash code ng field at pagkatapos ay idagdag ang kabuuan ng iba pang dalawang numerical na field dito. Ang mga developer ng Java ay may simpleng trick na ginagamit nila upang bawasan ang bilang ng mga banggaan: kapag nag-compute ng hash code, i-multiply ang intermediate na resulta sa isang kakaibang prime. Ang pinakakaraniwang ginagamit na numero ay 29 o 31. Hindi namin susuriin ang mga subtlety ng matematika sa ngayon, ngunit sa hinaharap tandaan na ang pagpaparami ng mga intermediate na resulta sa isang sapat na malaking kakaibang numero ay nakakatulong na "ipakalat" ang mga resulta ng hash function at, dahil dito, bawasan ang bilang ng mga bagay na may parehong hash code. Para sa aming hashCode()pamamaraan sa LuxuryAuto, magiging ganito ang hitsura:

@Override
public int hashCode() {
   int result = model == null ? 0 : model.hashCode();
   result = 31 * result + manufactureYear;
   result = 31 * result + dollarPrice;
   return result;
}
Maaari kang magbasa nang higit pa tungkol sa lahat ng mga intricacies ng mekanismong ito sa post na ito sa StackOverflow , pati na rin sa aklat na Effective Java ni Joshua Bloch. Sa wakas, isa pang mahalagang punto na dapat banggitin. Sa tuwing malalampasan namin ang equals()at hashCode()pamamaraan, pumili kami ng ilang partikular na field ng instance na isinasaalang-alang sa mga pamamaraang ito. Ang mga pamamaraang ito ay isinasaalang-alang ang parehong mga larangan. Ngunit maaari ba nating isaalang-alang ang iba't ibang larangan sa equals()at hashCode()? Sa teknikal, kaya natin. Ngunit ito ay isang masamang ideya, at narito kung bakit:

@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;
}
Narito ang aming equals()at hashCode()mga pamamaraan para sa LuxuryAutoklase. Ang hashCode()pamamaraan ay nanatiling hindi nagbabago, ngunit inalis namin ang modelfield mula sa equals()pamamaraan. Ang modelo ay hindi na isang katangiang ginagamit kapag ang equals()pamamaraan ay naghahambing ng dalawang bagay. Ngunit kapag kinakalkula ang hash code, ang field na iyon ay isinasaalang-alang pa rin. Ano ang makukuha natin bilang resulta? Gumawa tayo ng dalawang kotse at alamin!

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());
   }
}

Are these two objects equal to each other? 
true 
What are their hash codes? 
-1372326051 
1668702472
Error! Sa pamamagitan ng paggamit ng iba't ibang field para sa equals()at hashCode()mga pamamaraan, nilabag namin ang mga kontratang naitatag para sa kanila! Ang dalawang bagay na pantay ayon sa equals()pamamaraan ay dapat magkaroon ng parehong hash code. Nakatanggap kami ng iba't ibang halaga para sa kanila. Ang ganitong mga error ay maaaring humantong sa ganap na hindi kapani-paniwalang mga kahihinatnan, lalo na kapag nagtatrabaho sa mga koleksyon na gumagamit ng hash. Bilang resulta, kapag na-override mo equals()at hashCode(), dapat mong isaalang-alang ang parehong mga field. Medyo mahaba ang araling ito, ngunit marami kang natutunan ngayon! :) Ngayon ay oras na upang bumalik sa paglutas ng mga gawain!
Mga komento
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION