JavaRush /Java Blog /Random-TL /Katumbas at hashCode na mga kontrata o anuman ito
Aleksandr Zimin
Antas
Санкт-Петербург

Katumbas at hashCode na mga kontrata o anuman ito

Nai-publish sa grupo
Ang karamihan sa mga programmer ng Java, siyempre, ay alam na ang mga pamamaraan equalsay hashCodemalapit na nauugnay sa isa't isa, at na ipinapayong i-override ang parehong mga pamamaraan na ito sa kanilang mga klase nang tuluy-tuloy. Ang isang bahagyang mas maliit na bilang ay nakakaalam kung bakit ganito at kung anong malungkot na kahihinatnan ang maaaring mangyari kung nilabag ang panuntunang ito. Iminumungkahi kong isaalang-alang ang konsepto ng mga pamamaraang ito, ulitin ang kanilang layunin at maunawaan kung bakit sila konektado. Isinulat ko ang artikulong ito, tulad ng nauna tungkol sa pag-load ng mga klase, para sa aking sarili upang sa wakas ay maihayag ang lahat ng mga detalye ng isyu at hindi na bumalik sa mga mapagkukunan ng third-party. Samakatuwid, ako ay natutuwa sa constructive criticism, dahil kung may mga puwang sa isang lugar, dapat silang alisin. Ang artikulo, sayang, naging medyo mahaba.

katumbas ng override rules

Ang isang pamamaraan equals()ay kinakailangan sa Java upang kumpirmahin o tanggihan ang katotohanan na ang dalawang bagay na may parehong pinagmulan ay lohikal na magkapareho . Iyon ay, kapag naghahambing ng dalawang bagay, kailangang maunawaan ng programmer kung ang kanilang mga makabuluhang field ay katumbas . Hindi kinakailangan na ang lahat ng mga patlang ay dapat na magkapareho, dahil ang pamamaraan ay equals()nagpapahiwatig ng lohikal na pagkakapantay-pantay . Ngunit kung minsan ay walang partikular na pangangailangan na gamitin ang pamamaraang ito. Tulad ng sinasabi nila, ang pinakamadaling paraan upang maiwasan ang mga problema gamit ang isang partikular na mekanismo ay hindi ang paggamit nito. Dapat ding tandaan na sa sandaling masira mo ang isang kontrata, equalsmawawalan ka ng kontrol sa pag-unawa kung paano makikipag-ugnayan ang ibang mga bagay at istruktura sa iyong bagay. At kasunod na paghahanap ng sanhi ng error ay magiging napakahirap.

Kailan hindi dapat i-override ang paraang ito

  • Kapag ang bawat instance ng isang klase ay natatangi.
  • Sa mas malaking lawak, nalalapat ito sa mga klaseng iyon na nagbibigay ng partikular na gawi sa halip na idinisenyo upang gumana sa data. Tulad, halimbawa, bilang klase Thread. Para sa kanila equals, ang pagpapatupad ng pamamaraang ibinigay ng klase Objectay higit pa sa sapat. Ang isa pang halimbawa ay ang mga klase ng enum ( Enum).
  • Kung sa katunayan ang klase ay hindi kinakailangan upang matukoy ang pagkakapareho ng mga pagkakataon nito.
  • Halimbawa, para sa isang klase java.util.Randomay hindi na kailangang ihambing ang mga pagkakataon ng klase sa isa't isa, na tinutukoy kung maaari nilang ibalik ang parehong pagkakasunud-sunod ng mga random na numero. Dahil lang sa katangian ng klase na ito ay hindi man lang nagpapahiwatig ng ganoong pag-uugali.
  • Kapag ang klase na iyong pinalawig ay mayroon nang sariling pagpapatupad ng pamamaraan equalsat ang pag-uugali ng pagpapatupad na ito ay nababagay sa iyo.
  • Halimbawa, para sa mga klase Set, List, Mapang pagpapatupad equalsay nasa AbstractSet, AbstractListat AbstractMapayon sa pagkakabanggit.
  • At sa wakas, hindi na kailangang i-override equalskapag ang saklaw ng iyong klase ay privateo package-privateat sigurado ka na ang pamamaraang ito ay hindi kailanman matatawag.

katumbas ng kontrata

Kapag nag-o-override ng isang paraan, equalsdapat sumunod ang developer sa mga pangunahing panuntunang tinukoy sa detalye ng wikang Java.
  • Reflexivity
  • para sa anumang ibinigay na halaga x, ang expression x.equals(x)ay dapat bumalik true.
    Given - ibig sabihin ganyanx != null
  • Simetrya
  • para sa anumang ibinigay na mga halaga xat y, x.equals(y)ay dapat bumalik truelamang kung ito ay y.equals(x)bumalik true.
  • Transitivity
  • para sa anumang ibinigay na mga halaga x, yat z, kung x.equals(y)bumalik trueat y.equals(z)bumalik true, x.equals(z)dapat ibalik ang halaga true.
  • Hindi pagbabago
  • para sa anumang ibinigay na mga halaga, xat ibabalik ng ypaulit-ulit na tawag ang halaga ng nakaraang tawag sa paraang ito, sa kondisyon na ang mga field na ginamit upang ihambing ang dalawang bagay ay hindi nagbabago sa pagitan ng mga tawag.x.equals(y)
  • Paghahambing null
  • para sa anumang ibinigay na halaga xang tawag x.equals(null)ay dapat bumalik false.

katumbas ng paglabag sa kontrata

Maraming mga klase, tulad ng mga mula sa Java Collections Framework, ay nakasalalay sa pagpapatupad ng pamamaraan equals(), kaya hindi mo ito dapat pabayaan, dahil Ang paglabag sa kontrata ng pamamaraang ito ay maaaring humantong sa hindi makatwiran na operasyon ng aplikasyon, at sa kasong ito ay magiging mahirap hanapin ang dahilan. Ayon sa prinsipyo ng reflexivity , ang bawat bagay ay dapat na katumbas ng sarili nito. Kung nilabag ang prinsipyong ito, kapag nagdagdag kami ng isang bagay sa koleksyon at pagkatapos ay hinanap ito gamit ang pamamaraan, contains()hindi namin mahahanap ang bagay na idinagdag namin sa koleksyon. Ang kondisyon ng symmetry ay nagsasaad na ang anumang dalawang bagay ay dapat na pantay-pantay anuman ang pagkakasunud-sunod kung saan sila inihambing. Halimbawa, kung mayroon kang isang klase na naglalaman lamang ng isang field ng uri ng string, magiging mali na ihambing ang equalsfield na ito sa isang string sa isang paraan. kasi sa kaso ng baligtad na paghahambing, palaging ibabalik ng pamamaraan ang halaga 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);
}
Mula sa kondisyon ng transitivity sumusunod na kung alinman sa dalawa sa tatlong mga bagay ay pantay, kung gayon sa kasong ito ang lahat ng tatlo ay dapat na pantay. Ang prinsipyong ito ay madaling malalabag kapag kinakailangan na palawigin ang isang partikular na base class sa pamamagitan ng pagdaragdag ng isang makabuluhang bahagi dito . Halimbawa, sa isang klase Pointna may mga coordinate xat ykailangan mong idagdag ang kulay ng punto sa pamamagitan ng pagpapalawak nito. Upang gawin ito, kakailanganin mong magdeklara ng klase ColorPointna may naaangkop na field color. Kaya, kung sa pinalawig na klase tinatawag namin ang equalsparaan ng magulang, at sa magulang ay ipinapalagay namin na ang mga coordinate lamang xat inihambing y, kung gayon ang dalawang punto ng magkakaibang kulay ngunit may parehong mga coordinate ay ituturing na pantay, na hindi tama. Sa kasong ito, kinakailangan upang turuan ang nagmula na klase upang makilala ang mga kulay. Upang gawin ito, maaari kang gumamit ng dalawang pamamaraan. Ngunit ang isa ay lalabag sa tuntunin ng mahusay na proporsyon , at ang pangalawang - transitivity .
// Первый способ, нарушая симметричность
// Метод переопределен в классе ColorPoint
@Override
public boolean equals(Object o) {
    if (!(o instanceof ColorPoint)) return false;
    return super.equals(o) && ((ColorPoint) o).color == color;
}
point.equals(colorPoint)Sa kasong ito, ibabalik ng tawag ang halaga true, at colorPoint.equals(point)babalik ang paghahambing false, dahil inaasahan ang isang bagay ng "nito" klase. Kaya, ang panuntunan ng mahusay na proporsyon ay nilabag. Ang pangalawang paraan ay nagsasangkot ng paggawa ng "bulag" na pagsusuri sa kaso kapag walang data tungkol sa kulay ng punto, ibig sabihin, mayroon kaming klase Point. O suriin ang kulay kung magagamit ang impormasyon tungkol dito, iyon ay, ihambing ang isang bagay ng klase 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;
}
Ang prinsipyo ng transitivity ay nilabag dito bilang mga sumusunod. Sabihin nating mayroong kahulugan ng mga sumusunod na bagay:
ColorPoint p1 = new ColorPoint(1, 2, Color.RED);
Point p2 = new Point(1, 2);
ColorPoint p3 = new ColorPoint(1, 2, Color.BLUE);
Kaya, kahit na ang pagkakapantay-pantay p1.equals(p2)at nasiyahan p2.equals(p3), p1.equals(p3)ibabalik nito ang halaga false. Kasabay nito, ang pangalawang paraan, sa palagay ko, ay mukhang hindi gaanong kaakit-akit, dahil Sa ilang mga kaso, maaaring mabulag ang algorithm at hindi ganap na maisagawa ang paghahambing, at maaaring hindi mo alam ang tungkol dito. Kaunting tula Sa pangkalahatan, sa pagkakaintindi ko, walang konkretong solusyon sa problemang ito. May isang opinyon mula sa isang awtoritatibong may-akda na nagngangalang Kay Horstmann na maaari mong palitan ang paggamit ng operator instanceofng isang method call getClass()na nagbabalik sa klase ng object at, bago mo simulan ang paghahambing ng mga bagay sa kanilang sarili, siguraduhin na ang mga ito ay pareho ang uri , at huwag pansinin ang katotohanan ng kanilang karaniwang pinagmulan. Kaya, ang mga patakaran ng simetrya at transitivity ay masisiyahan. Ngunit sa parehong oras, sa kabilang panig ng barikada ay nakatayo ang isa pang may-akda, na hindi gaanong iginagalang sa malawak na mga bilog, si Joshua Bloch, na naniniwala na ang pamamaraang ito ay lumalabag sa prinsipyo ng pagpapalit ng Barbara Liskov. Ang prinsipyong ito ay nagsasaad na "ang code sa pagtawag ay dapat tratuhin ang isang base class sa parehong paraan tulad ng mga subclass nito nang hindi nalalaman ito . " At sa solusyon na iminungkahi ni Horstmann, ang prinsipyong ito ay malinaw na nilabag, dahil ito ay nakasalalay sa pagpapatupad. Sa madaling salita, malinaw na madilim ang usapin. Dapat ding tandaan na nilinaw ni Horstmann ang panuntunan para sa paglalapat ng kanyang diskarte at nagsusulat sa simpleng Ingles na kailangan mong magpasya sa isang diskarte kapag nagdidisenyo ng mga klase, at kung ang equality testing ay isasagawa lamang ng superclass, magagawa mo ito sa pamamagitan ng pagsasagawa ang operasyon instanceof. Kung hindi, kapag ang mga semantika ng tseke ay nagbabago depende sa nagmula na klase at ang pagpapatupad ng pamamaraan ay kailangang ilipat pababa sa hierarchy, dapat mong gamitin ang pamamaraan getClass(). Si Joshua Bloch, naman, ay nagmumungkahi na abandunahin ang mana at gumamit ng komposisyon ng bagay sa pamamagitan ng pagsasama ng isang ColorPointklase sa klase Pointat pagbibigay ng paraan ng pag-access asPoint()upang makakuha ng partikular na impormasyon tungkol sa punto. Maiiwasan nito ang paglabag sa lahat ng mga patakaran, ngunit, sa palagay ko, gagawin nitong mas mahirap na maunawaan ang code. Ang ikatlong opsyon ay ang paggamit ng awtomatikong henerasyon ng katumbas na paraan gamit ang IDE. Ang ideya, sa pamamagitan ng paraan, ay nagpaparami ng henerasyon ng Horstmann, na nagbibigay-daan sa iyong pumili ng isang diskarte para sa pagpapatupad ng isang pamamaraan sa isang superclass o sa mga inapo nito. Sa wakas, ang susunod na panuntunan sa pagkakapare-pareho ay nagsasaad na kahit na ang mga bagay ay xhindi ynagbabago, ang pagtawag sa kanila muli x.equals(y)ay dapat na ibalik ang parehong halaga tulad ng dati. Ang panghuling tuntunin ay walang bagay na dapat na katumbas ng null. Ang lahat ay malinaw dito null- ito ay kawalan ng katiyakan, ang bagay ba ay katumbas ng kawalan ng katiyakan? Hindi malinaw, ibig sabihin false.

Pangkalahatang algorithm para sa pagtukoy ng mga katumbas

  1. Suriin ang pagkakapantay-pantay ng mga sanggunian sa bagay thisat mga parameter ng pamamaraan o.
    if (this == o) return true;
  2. Suriin kung ang link ay tinukoy o, ibig sabihin, kung ito ay null.
    Kung sa hinaharap, kapag naghahambing ng mga uri ng bagay, ang operator ay gagamitin instanceof, ang item na ito ay maaaring laktawan, dahil ang parameter na ito ay bumalik falsesa kasong ito null instanceof Object.
  3. Ihambing ang mga uri ng bagay thisgamit oang isang operator instanceofo pamamaraan getClass(), na ginagabayan ng paglalarawan sa itaas at ng iyong sariling intuwisyon.
  4. Kung ang isang paraan equalsay na-override sa isang subclass, siguraduhing tumawagsuper.equals(o)
  5. I-convert ang uri ng parameter osa kinakailangang klase.
  6. Magsagawa ng paghahambing ng lahat ng makabuluhang object field:
    • para sa mga primitive na uri (maliban floatsa at double), gamit ang operator==
    • para sa mga patlang ng sanggunian kailangan mong tawagan ang kanilang pamamaraanequals
    • para sa mga array, maaari mong gamitin ang cyclic iteration o ang paraanArrays.equals()
    • para sa mga uri floatat doublekinakailangang gumamit ng mga paraan ng paghahambing ng kaukulang mga klase ng wrapper Float.compare()atDouble.compare()
  7. At panghuli, sagutin ang tatlong tanong: simetriko ba ang ipinatupad na paraan ? Palipat ? Sumang-ayon ? Ang iba pang dalawang prinsipyo ( reflexivity at katiyakan ) ay karaniwang awtomatikong isinasagawa.

Mga panuntunan sa pag-override ng HashCode

Ang hash ay isang numerong nabuo mula sa isang bagay na naglalarawan sa estado nito sa isang punto ng oras. Ang numerong ito ay ginagamit sa Java pangunahin sa mga hash table gaya ng HashMap. Sa kasong ito, ang hash function ng pagkuha ng isang numero batay sa isang bagay ay dapat ipatupad sa paraang masiguro ang isang medyo pantay na pamamahagi ng mga elemento sa hash table. At upang mabawasan din ang posibilidad ng mga banggaan kapag ang function ay nagbabalik ng parehong halaga para sa iba't ibang mga susi.

HashCode ng kontrata

Upang magpatupad ng hash function, tinutukoy ng detalye ng wika ang mga sumusunod na panuntunan:
  • Ang pagtawag sa isang pamamaraan hashCodeng isa o higit pang beses sa parehong bagay ay dapat magbalik ng parehong halaga ng hash, sa kondisyon na ang mga patlang ng bagay na kasangkot sa pagkalkula ng halaga ay hindi nagbago.
  • Ang pagtawag sa isang pamamaraan hashCodesa dalawang bagay ay dapat palaging ibalik ang parehong numero kung ang mga bagay ay pantay (ang pagtawag sa isang paraan equalssa mga bagay na ito ay nagbabalik true).
  • Ang pagtawag sa isang pamamaraan hashCodesa dalawang hindi pantay na bagay ay dapat magbalik ng magkaibang mga halaga ng hash. Bagama't hindi sapilitan ang pangangailangang ito, dapat isaalang-alang na ang pagpapatupad nito ay magkakaroon ng positibong epekto sa pagganap ng mga hash table.

Ang mga katumbas at hashCode na pamamaraan ay dapat na ma-override nang magkasama

Batay sa mga kontratang inilarawan sa itaas, sumusunod na kapag na-override ang pamamaraan sa iyong code equals, dapat mong palaging i-override ang pamamaraan hashCode. Dahil sa katunayan ang dalawang pagkakataon ng isang klase ay magkaiba dahil sila ay nasa magkaibang lugar ng memorya, kailangan nilang ikumpara ayon sa ilang lohikal na pamantayan. Alinsunod dito, dapat ibalik ng dalawang lohikal na katumbas na bagay ang parehong halaga ng hash. Ano ang mangyayari kung isa lamang sa mga pamamaraang ito ang ma-override?
  1. equalsOo hashCodehindi

    Sabihin nating tama naming tinukoy ang isang pamamaraan equalssa aming klase, at hashCodenagpasya na iwanan ang pamamaraan na ito ay nasa klase Object. Pagkatapos mula sa punto ng view ng pamamaraan equalsang dalawang bagay ay magiging lohikal na pantay, habang mula sa punto ng view ng pamamaraan hashCodeay wala silang magkakatulad. At sa gayon, sa pamamagitan ng paglalagay ng isang bagay sa isang hash table, nagkakaroon tayo ng panganib na hindi ito maibalik sa pamamagitan ng key.
    Halimbawa, tulad nito:

    Map<Point, String> m = new HashMap<>();
    m.put(new Point(1, 1),Point A);
    // pointName == null
    String pointName = m.get(new Point(1, 1));

    Malinaw, ang bagay na inilalagay at ang bagay na hinahanap ay dalawang magkaibang bagay, bagama't sila ay lohikal na pantay. Pero, kasi mayroon silang iba't ibang mga halaga ng hash dahil nilabag namin ang kontrata, maaari naming sabihin na nawala namin ang aming bagay sa isang lugar sa bituka ng hash table.

  2. hashCodeOo equalshindi.

    Ano ang mangyayari kung i-override natin ang pamamaraan hashCodeat equalsmamanahin ang pagpapatupad ng pamamaraan mula sa klase Object. Tulad ng alam mo, ang equalsdefault na paraan ay naghahambing lamang ng mga pointer sa mga bagay, na tinutukoy kung ang mga ito ay tumutukoy sa parehong bagay. Ipagpalagay natin na hashCodeisinulat natin ang pamamaraan ayon sa lahat ng mga canon, ibig sabihin, nabuo ito gamit ang IDE, at ibabalik nito ang parehong mga halaga ng hash para sa lohikal na magkaparehong mga bagay. Malinaw, sa pamamagitan ng paggawa nito natukoy na namin ang ilang mekanismo para sa paghahambing ng dalawang bagay.

    Samakatuwid, ang halimbawa mula sa nakaraang talata ay dapat isagawa sa teorya. Ngunit hindi pa rin namin mahahanap ang aming bagay sa hash table. Bagaman magiging malapit tayo dito, dahil sa pinakamababa ay makakahanap tayo ng hash table basket kung saan ang bagay ay magsisinungaling.

    Upang matagumpay na maghanap ng isang bagay sa isang talahanayan ng hash, bilang karagdagan sa paghahambing ng mga halaga ng hash ng susi, ginagamit din ang pagpapasiya ng lohikal na pagkakapantay-pantay ng susi sa hinanap na bagay. Iyon ay, equalswalang paraan upang gawin nang walang overriding ang pamamaraan.

Pangkalahatang algorithm para sa pagtukoy ng hashCode

Dito, tila sa akin, hindi ka dapat mag-alala nang labis at bumuo ng pamamaraan sa iyong paboritong IDE. Dahil ang lahat ng mga paglilipat ng mga piraso sa kanan at kaliwa sa paghahanap ng gintong ratio, ibig sabihin, normal na pamamahagi - ito ay para sa ganap na matigas ang ulo dudes. Sa personal, duda ako na magagawa ko nang mas mahusay at mas mabilis kaysa sa parehong Ideya.

Sa halip na isang konklusyon

Kaya, nakikita namin na ang mga pamamaraan equalsay gumaganap hashCodeng isang mahusay na tinukoy na papel sa wikang Java at idinisenyo upang makuha ang lohikal na pagkakapantay-pantay na katangian ng dalawang bagay. Sa kaso ng pamamaraan, equalsito ay may direktang kaugnayan sa paghahambing ng mga bagay, sa kaso ng hashCodehindi direktang isa, kapag kinakailangan, sabihin nating, upang matukoy ang tinatayang lokasyon ng isang bagay sa mga hash table o katulad na mga istruktura ng data upang dagdagan ang bilis ng paghahanap ng isang bagay. Bilang karagdagan sa mga kontrata , equalsmay hashCodeisa pang kinakailangan na nauugnay sa paghahambing ng mga bagay. Ito ang pagkakapare-pareho ng isang paraan compareTong interface Comparablena may isang equals. Ang kinakailangang ito ay nag-oobliga sa developer na palaging bumalik x.equals(y) == truekapag x.compareTo(y) == 0. Iyon ay, nakikita natin na ang lohikal na paghahambing ng dalawang bagay ay hindi dapat sumalungat saanman sa aplikasyon at dapat palaging pare-pareho.

Mga pinagmumulan

Epektibong Java, Ikalawang Edisyon. Joshua Bloch. Libreng pagsasalin ng isang napakagandang libro. Java, library ng isang propesyonal. Tomo 1. Mga Pangunahing Kaalaman. Kay Horstmann. Mas kaunting teorya at mas maraming kasanayan. Ngunit ang lahat ay hindi nasusuri sa mas maraming detalye gaya ng kay Bloch. Bagama't may pagtingin sa parehong katumbas (). Mga istruktura ng data sa mga larawan. HashMap Isang lubhang kapaki-pakinabang na artikulo sa HashMap device sa Java. Sa halip na tumingin sa mga mapagkukunan.
Mga komento
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION