JavaRush /Java Blog /Random-TL /equals & hashCode method: practice of use

equals & hashCode method: practice of use

Nai-publish sa grupo
Kamusta! Ngayon ay pag-uusapan natin ang tungkol sa dalawang mahahalagang pamamaraan sa Java - equals()at hashCode(). Hindi ito ang unang pagkakataon na nakilala namin sila: sa simula ng kursong JavaRush ay nagkaroon ng maikling lecture tungkol sa equals()- basahin ito kung nakalimutan mo na ito o hindi mo pa nakikita. Ang mga pamamaraan ay katumbas ng &  hashCode: kasanayan sa paggamit - 1Sa aralin ngayon, pag-uusapan natin ang mga konseptong ito nang detalyado - maniwala ka sa akin, maraming pag-uusapan! At bago tayo magpatuloy sa isang bagong bagay, i-refresh natin ang ating memorya sa kung ano ang natalakay na natin :) Tulad ng naaalala mo, ang karaniwang paghahambing ng dalawang bagay gamit ang ==operator na " " ay isang masamang ideya, dahil ang " ==" ay naghahambing ng mga sanggunian. Narito ang aming halimbawa sa mga kotse mula sa isang kamakailang lecture:
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 magkaparehong bagay ng klase Car: lahat ng field sa dalawang makina ay pareho, ngunit mali pa rin ang resulta ng paghahambing. Alam na natin ang dahilan: ang mga link car1at car2tumuturo sa iba't ibang mga address sa memorya, kaya hindi sila pantay. Gusto pa rin naming ihambing ang dalawang bagay, hindi dalawang sanggunian. Ang pinakamahusay na solusyon para sa paghahambing ng mga bagay ay ang equals().

katumbas ng() na pamamaraan

Maaari mong tandaan na hindi namin nililikha ang pamamaraang ito mula sa simula, ngunit i-override ito - pagkatapos ng lahat, ang pamamaraan equals()ay tinukoy sa klase Object. Gayunpaman, sa karaniwang anyo nito ay hindi gaanong ginagamit:
public boolean equals(Object obj) {
   return (this == obj);
}
Ito ay kung paano equals()tinukoy ang pamamaraan sa klase Object. Ang parehong paghahambing ng mga link. Bakit siya ginawang ganito? 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 ideya ng pamamaraan equals()- tinutukoy mismo ng tagalikha ng klase ang mga katangian kung saan sinusuri ang pagkakapantay-pantay ng mga bagay ng klase na ito. Sa paggawa nito, na-override mo ang pamamaraan equals()sa iyong klase. Kung hindi mo lubos na nauunawaan ang kahulugan ng "ikaw mismo ang tumutukoy sa mga katangian," tingnan natin ang isang halimbawa. Narito ang isang simpleng klase ng tao - 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.
}
Sabihin nating nagsusulat kami ng isang programa na kailangang matukoy kung ang dalawang tao ay magkamag-anak ng kambal, o mga doppelgänger lang. Mayroon kaming limang katangian: laki ng ilong, kulay ng mata, hairstyle, pagkakaroon ng mga peklat at ang mga resulta ng isang DNA biological test (para sa pagiging simple - sa anyo ng isang numero ng code). Alin sa mga katangiang ito ang sa tingin mo ay magbibigay-daan sa aming programa na makilala ang kambal na kamag-anak? Ang mga pamamaraan ay katumbas ng &  hashCode: kasanayan sa paggamit - 2Siyempre, isang biological test lamang ang makakapagbigay ng garantiya. Ang dalawang tao ay maaaring magkaroon ng parehong kulay ng mata, hairstyle, ilong, at kahit na mga peklat - maraming tao sa mundo, at imposibleng maiwasan ang mga pagkakataon. Kailangan natin ng maaasahang mekanismo: tanging ang resulta ng pagsusuri sa DNA ang nagpapahintulot sa atin na makagawa ng tumpak na konklusyon. Ano ang ibig sabihin nito para sa aming pamamaraan equals()? Kailangan nating muling tukuyin ito sa isang klase Manna isinasaalang-alang ang mga kinakailangan ng ating programa. Ang pamamaraan ay dapat ihambing ang larangan int dnaCodeng dalawang bagay, at 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;
}
Ganyan ba talaga kasimple? Hindi naman. May na-miss kami. Sa kasong ito, para sa aming mga bagay ay tinukoy lamang namin ang isang "makabuluhang" patlang kung saan itinatag ang kanilang pagkakapantay-pantay - dnaCode. Ngayon isipin na hindi tayo magkakaroon ng 1, ngunit 50 tulad ng "makabuluhang" mga patlang. At kung ang lahat ng 50 mga patlang ng dalawang bagay ay pantay, kung gayon ang mga bagay ay pantay. Maaari rin itong mangyari. Ang pangunahing problema ay ang pagkalkula ng pagkakapantay-pantay ng 50 mga patlang ay isang proseso ng pag-ubos ng oras at pag-ubos ng mapagkukunan. Ngayon isipin na bilang karagdagan sa klase, Manmayroon kaming isang klase Womanna may eksaktong parehong mga patlang tulad ng sa Man. At kung ang isa pang programmer ay gumagamit ng iyong mga klase, madali siyang makakasulat sa kanyang programa ng isang bagay tulad ng:
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));
}
Sa kasong ito, walang saysay na suriin ang mga halaga ng patlang: nakikita natin na tinitingnan natin ang mga bagay ng dalawang magkaibang klase, at hindi sila maaaring magkapantay sa prinsipyo! Nangangahulugan ito na kailangan nating maglagay ng tseke sa pamamaraan equals()—paghahambing ng mga bagay ng dalawang magkaparehong klase. Buti naman naisip natin to!
@Override
public boolean equals(Object o) {
   if (getClass() != o.getClass()) return false;
   Man man = (Man) o;
   return dnaCode == man.dnaCode;
}
Pero baka may iba pa tayong nakalimutan? Hmm... Sa pinakamababa, dapat nating suriin na hindi natin inihahambing ang bagay sa sarili nito! Kung ang mga reference na A at B ay tumuturo sa parehong address sa memorya, kung gayon ang mga ito ay iisang bagay, at hindi rin natin kailangang mag-aksaya ng oras sa paghahambing 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;
}
Bilang karagdagan, hindi makakasamang magdagdag ng tseke para sa null: walang bagay na maaaring katumbas ng null, kung saan walang punto sa mga karagdagang pagsusuri. Isinasaalang-alang ang lahat ng ito, magiging ganito ang paraan ng aming equals()klase :Man
@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;
}
Isinasagawa namin ang lahat ng mga paunang pagsusuri na binanggit sa itaas. Kung ito ay lumabas na:
  • pinaghahambing namin ang dalawang bagay ng parehong klase
  • hindi ito ang parehong bagay
  • hindi namin inihahambing ang aming bagay sanull
...pagkatapos ay nagpapatuloy tayo sa paghahambing ng mga makabuluhang katangian. Sa aming kaso, ang mga patlang dnaCodeng dalawang bagay. Kapag ino-override ang isang paraan equals(), tiyaking sumunod sa mga kinakailangang ito:
  1. Reflexivity.

    Ang anumang bagay ay dapat equals()sa sarili nito.
    Isinasaalang-alang na namin ang pangangailangang ito. Ang aming pamamaraan ay nagsasaad:

    if (this == o) return true;

  2. Simetrya.

    Kung a.equals(b) == true, pagkatapos ay b.equals(a)dapat itong 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, kung gayon ang tseke b.equals(c)ay dapat ding bumalik na totoo.

  4. Permanence.

    Ang mga resulta ng trabaho equals()ay dapat magbago lamang kapag ang mga patlang na kasama dito ay nagbago. Kung ang data ng dalawang bagay ay hindi nagbago, ang mga resulta ng pagsusuri equals()ay dapat palaging pareho.

  5. Hindi pagkakapantay-pantay sa null.

    Para sa anumang bagay, ang tseke a.equals(null)ay dapat magbalik ng mali.
    Ito ay hindi lamang isang hanay ng ilang "mga kapaki-pakinabang na rekomendasyon", ngunit isang mahigpit na kontrata ng mga pamamaraan , na inireseta sa dokumentasyon ng Oracle

hashCode() na pamamaraan

Ngayon pag-usapan natin ang pamamaraan hashCode(). Bakit kailangan ito? Eksaktong para sa parehong layunin - paghahambing ng mga bagay. Pero meron na tayo equals()! Bakit ibang paraan? Ang sagot ay simple: upang mapabuti ang pagiging produktibo. Ang isang hash function, na kinakatawan ng , method sa Java hashCode(), ay nagbabalik ng fixed-length na numeric na halaga para sa anumang bagay. Sa kaso ng Java, ang pamamaraan hashCode()ay nagbabalik ng 32-bit na numero ng uri int. Ang paghahambing ng dalawang numero sa isa't isa ay mas mabilis kaysa sa paghahambing ng dalawang bagay gamit ang pamamaraan equals(), lalo na kung gumagamit ito ng maraming field. Kung ihahambing ng aming programa ang mga bagay, mas madaling gawin ito sa pamamagitan ng hash code, at kung magkapantay lamang sila sa pamamagitan ng hashCode()- magpatuloy sa paghahambing sa pamamagitan ng equals(). Ito pala, kung paano gumagana ang hash-based na mga istruktura ng data—halimbawa, ang alam mo HashMap! Ang pamamaraan hashCode(), tulad ng equals(), ay na-override ng developer mismo. At tulad ng para sa equals(), ang pamamaraan hashCode()ay may mga opisyal na kinakailangan na tinukoy sa dokumentasyon ng Oracle:
  1. Kung ang dalawang bagay ay pantay (iyon ay, ang pamamaraan equals()ay nagbabalik ng totoo), dapat silang magkaroon ng parehong hash code.

    Kung hindi, ang ating mga pamamaraan ay magiging walang kabuluhan. Ang pagsuri sa pamamagitan ng hashCode(), gaya ng sinabi namin, ay dapat mauna 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 (tulad ng tinukoy namin sa pamamaraan equals()).

  2. Kung ang isang pamamaraan hashCode()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. Maaaring magkaroon ng parehong hash code ang dalawang magkaibang bagay.

Ang ikatlong tuntunin ay medyo nakakalito. Paanong nangyari to? Ang paliwanag ay medyo simple. Ang pamamaraan hashCode()ay nagbabalik int. intay isang 32-bit na numero. Mayroon itong limitadong bilang ng mga halaga - mula -2,147,483,648 hanggang +2,147,483,647. Sa madaling salita, mayroon lamang mahigit 4 na bilyong variation ng numero int. Ngayon isipin na lumilikha ka ng isang programa upang mag-imbak ng data tungkol sa lahat ng nabubuhay na tao sa Earth. Ang bawat tao ay magkakaroon ng sariling bagay sa klase Man. ~7.5 bilyong tao ang naninirahan sa mundo. Sa madaling salita, gaano man kahusay ang isang algorithm Manna isinulat namin para sa pag-convert ng mga bagay sa mga numero, hindi lang kami magkakaroon ng sapat na mga numero. Mayroon lang tayong 4.5 bilyong opsyon, at marami pang tao. Nangangahulugan ito na gaano man tayo kahirap, ang mga hash code ay magiging pareho para sa ilang iba't ibang tao. Ang sitwasyong ito (ang mga hash code ng dalawang magkaibang bagay na tumutugma) ay tinatawag na banggaan. Ang isa sa mga layunin ng programmer kapag nag-o-override sa isang paraan hashCode()ay upang mabawasan ang potensyal na bilang ng mga banggaan hangga't maaari. Ano ang magiging hitsura ng aming pamamaraan hashCode()para sa klase Man, na isinasaalang-alang ang lahat ng mga patakarang ito? Ganito:
@Override
public int hashCode() {
   return dnaCode;
}
Nagulat? :) Unexpectedly, pero kung titingnan mo ang mga requirements, makikita mo na sumusunod kami sa lahat. Ang mga bagay kung saan ang sa amin equals()ay nagbabalik ng totoo ay magiging pantay sa hashCode(). Kung ang aming dalawang bagay Manay pantay sa halaga equals(iyon ay, mayroon silang parehong halaga dnaCode), ang aming pamamaraan ay magbabalik ng parehong numero. Tingnan natin ang isang mas kumplikadong halimbawa. Sabihin nating ang aming programa ay dapat pumili ng mga mamahaling sasakyan para sa mga kliyente ng kolektor. Ang pagkolekta ay isang kumplikadong bagay, at mayroong maraming mga tampok dito. Ang isang kotse mula 1963 ay maaaring nagkakahalaga ng 100 beses na mas mataas kaysa sa parehong kotse mula 1964. Ang isang pulang kotse mula 1970 ay maaaring nagkakahalaga ng 100 beses na mas mataas kaysa sa isang asul na kotse ng parehong gawa mula sa parehong taon. Ang mga pamamaraan ay katumbas ng &  hashCode: kasanayan sa paggamit - 4Sa unang kaso, sa klase Man, itinapon namin ang karamihan sa mga field (i.e., mga katangian ng tao) bilang hindi gaanong mahalaga at ginamit lang ang field para sa paghahambing dnaCode. Narito kami ay nagtatrabaho sa isang napaka-natatanging lugar, at hindi maaaring magkaroon ng maliliit na detalye! Narito ang aming klase 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.
}
Dito, kapag naghahambing, dapat nating isaalang-alang ang lahat ng mga patlang. Ang anumang pagkakamali ay maaaring magastos ng daan-daang libong dolyar para sa kliyente, kaya mas mabuting maging 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 pamamaraan, equals()hindi namin nakalimutan ang tungkol sa lahat ng mga tseke na napag-usapan namin kanina. Ngunit ngayon inihambing namin ang bawat isa sa tatlong mga patlang ng aming mga bagay. Sa programang ito, ang pagkakapantay-pantay ay dapat na ganap, sa bawat larangan. Paano kung hashCode?
@Override
public int hashCode() {
   int result = model == null ? 0 : model.hashCode();
   result = result + manufactureYear;
   result = result + dollarPrice;
   return result;
}
Ang patlang modelsa aming klase ay isang string. Ito ay maginhawa: Stringang pamamaraan hashCode()ay na-override na sa klase. Kinakalkula namin ang hash code ng field model, at dito idinaragdag namin ang kabuuan ng dalawa pang numeric na field. Mayroong isang maliit na trick sa Java na ginagamit upang bawasan ang bilang ng mga banggaan: kapag kinakalkula ang hash code, i-multiply ang intermediate na resulta sa isang kakaibang prime number. Ang pinakakaraniwang ginagamit na numero ay 29 o 31. Hindi na namin tatalakayin ang mga detalye ng matematika sa ngayon, ngunit para sa sanggunian sa hinaharap, tandaan na ang pag-multiply ng mga intermediate na resulta sa isang malaking sapat na kakaibang numero ay nakakatulong na "ipakalat" ang mga resulta ng hash function at nagtatapos sa mas kaunting mga bagay na may parehong hashcode. Para sa aming pamamaraan hashCode()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 ni Joshua Bloch na " Effective Java ". Sa wakas, may isa pang mahalagang punto na dapat banggitin. Sa bawat oras na kapag nag-o-override equals(), hashCode()pumili kami ng ilang partikular na field ng object, na isinasaalang-alang sa mga pamamaraang ito. 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 mga pamamaraan equals()para hashCode()sa klase ng LuxuryAuto. Ang pamamaraan hashCode()ay nanatiling hindi nagbabago, at equals()inalis namin ang field mula sa pamamaraan model. Ngayon ang modelo ay hindi isang katangian para sa paghahambing ng dalawang bagay sa pamamagitan ng equals(). Ngunit isinasaalang-alang pa rin ito kapag kinakalkula ang hash code. Ano ang makukuha natin bilang resulta? Gumawa tayo ng dalawang kotse at tingnan ito!
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
Error! Sa pamamagitan ng paggamit ng iba't ibang field para sa equals()at hashCode()nilabag namin ang kontratang itinatag para sa kanila! Ang dalawang pantay equals()na bagay ay dapat magkaroon ng parehong hash code. Nagkaroon tayo ng iba't ibang kahulugan para sa kanila. Ang ganitong mga error ay maaaring humantong sa mga hindi kapani-paniwalang kahihinatnan, lalo na kapag nagtatrabaho sa mga koleksyon na gumagamit ng mga hash. Samakatuwid, kapag muling tukuyin equals()at hashCode()magiging tama na gamitin ang parehong mga patlang. Ang lecture ay naging medyo mahaba, ngunit ngayon ay natutunan mo ang maraming mga bagong bagay! :) Oras na para bumalik sa paglutas ng mga problema!
Mga komento
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION