JavaRush /Java блогу /Random-KY /Equals жана hashCode келишимдери же кандай болсо да
Aleksandr Zimin
Деңгээл
Санкт-Петербург

Equals жана hashCode келишимдери же кандай болсо да

Группада жарыяланган
Java программисттеринин басымдуу көпчүлүгү, албетте, методдор бири-бири менен тыгыз байланышта экенин бorшет equalsжана hashCodeбул эки ыкманы тең өз класстарында ырааттуу түрдө жокко чыгаруу максатка ылайыктуу. Бир аз азыраак сан эмне үчүн мындай болгонун жана бул эреже бузулса, кандай кайгылуу кесепеттерге алып келиши мүмкүн экенин билет. Мен бул ыкмалардын концепциясын карап чыгууну, алардын максатын кайталоону жана эмне үчүн мынчалык тыгыз байланышта экенин түшүнүүнү сунуштайм. Мен бул макаланы, класстарды жүктөө жөнүндө мурункудай эле, маселенин бардык чоо-жайын ачып берүү жана үчүнчү тараптын булактарына кайрылбоо үчүн өзүм үчүн жаздым. Ошондуктан мен конструктивдүү сынга ыраазы болом, анткени бир жерде боштуктар болсо, аларды жоюу керек. Макала, тилекке каршы, абдан узун болуп чыкты.

жокко чыгаруу эрежелерине барабар

Бир эле келип чыккан эки an object логикалык жактан бирдейequals() экендигин тастыктоо же четке кагуу үчүн Javaда метод талап кылынат . Башкача айтканда, эки an objectти салыштырып жатканда, программист алардын маанилүү талаалары эквиваленттүү экендигин түшүнүшү керек . Бардык талаалар бирдей болушу шарт эмес, анткени метод логикалык теңчorкти билдирет . Бирок кээде бул ыкманы колдонуунун өзгөчө зарылдыгы жок. Алар айткандай, белгилүү бир механизмди колдонуу менен көйгөйлөрдөн качуунун эң оңой жолу - аны колдонбоо. Ошондой эле белгилей кетүү керек, сиз келишимди бузгандан кийин, сиз башка an objectтер жана структуралар сиздин an objectиңиз менен кандайча өз ара аракеттенишерин түшүнбөй каласыз. Анан катанын себебин табуу абдан кыйын болот. equals()equals

Бул ыкманы жокко чыгаруу үчүн эмес

  • Класстын ар бир мисалы уникалдуу болгондо.
  • Көбүрөөк даражада бул маалыматтар менен иштөө үчүн эмес, конкреттүү жүрүм-турумду камсыз кылган класстарга тиешелүү. Мындай, мисалы, класс Thread. Алар үчүн equalsкласс тарабынан берилген ыкманы ишке ашыруу Objectжетиштүү. Дагы бир мисал - энум класстары ( Enum).
  • Чындыгында класстан анын инстанцияларынын эквиваленттүүлүгүн аныктоо талап кылынбаганда.
  • Мисалы, класс үчүн java.util.Randomкласстын инстанцияларын бири-бири менен салыштыруунун эч кандай кажети жок, алар кокус сандардын бирдей ырааттуулугун кайтара алабы же жокпу аныктоо. Жөн гана, анткени бул класстын табияты мындай жүрүм-турумду билдирбейт.
  • Сиз кеңейтип жаткан класстын өзүнүн ыкмасын ишке ашырууга ээ болгондо equalsжана бул ишке ашыруунун жүрүм-туруму сизге ылайыктуу.
  • Мисалы, класстар үчүн Set, ишке ашыруу , жана Listтиешелүүлүгүнө жараша болот . MapequalsAbstractSetAbstractListAbstractMap
  • equalsАкыр-аягы, классыңыздын масштабы качан privateже болгондо жокко чыгаруунун кереги жок package-privateжана бул ыкма эч качан чакырылbyte деп ишенесиз.

келишимге барабар

Методду жокко чыгарууда, equalsиштеп чыгуучу Java тorнин спецификациясында аныкталган негизги эрежелерди сакташы керек.
  • Рефлексивдүүлүк
  • ар кандай берилген маани үчүн xтуюнтма x.equals(x)кайтып келиши керек true.
    Берилген - ушундай дегенди билдиретx != null
  • Симметрия
  • ар кандай берилген баалуулуктар үчүн xжана y, эгерде ал кайтып келсе гана x.equals(y)кайтарылышы керек . truey.equals(x)true
  • Өтмөлүк
  • ар кандай берилген баалуулуктар үчүн жана x, эгерде кайтарып берсе жана кайтарса , маанини кайтарышы керек . yzx.equals(y)truey.equals(z)truex.equals(z)true
  • ырааттуулук
  • ар кандай берилген баалуулуктар үчүн xжана yкайталанган чакыруу x.equals(y)эки an objectти салыштыруу үчүн колдонулган талаалар чалуулардын ортосунда өзгөрбөсө, бул ыкмага мурунку чалуунун маанисин кайтарат.
  • Салыштыруу null
  • кандайдыр бир маани үчүн xчалуу x.equals(null)кайтып келиши керек false.

келишимди бузууга барабар

Java Collections Framework сыяктуу көптөгөн класстар методдун ишке ашырылышына көз каранды equals(), андыктан ага көңүл бурбай коюуга болбойт, анткени Бул ыкманын келишимин бузуу өтүнмөнүн акылга сыйбаган иштешине алып келиши мүмкүн, жана бул учурда анын себебин табуу абдан кыйын болот. Рефлексивдүүлүк принцибине ылайык , ар бир an object өзүнө эквиваленттүү болушу керек. Эгерде бул принцип бузулса, коллекцияга an objectти кошуп, анан аны метод аркылуу издегенде, contains()коллекцияга жаңы эле кошкон an objectти таба албайбыз. Симметрия шарты эки an objectтин салыштырылган тартибине карабастан бирдей болушу керек экенин айтат. equalsМисалы, сизде сап түрүнүн бир гана талаасын камтыган классыңыз болсо, бул талааны методдогу сап менен салыштыруу туура эмес болот . Анткени тескери салыштыруу болгон учурда, ыкма ар дайым маанини кайтарып берет 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);
}
Өтмөлүк шартынан келип чыгат, эгерде үч an objectтин экөө тең болсо, анда бул учурда үчөө тең бирдей болушу керек. Белгилүү бир базалык класска маанилүү компонентти кошуу менен кеңейтүү зарыл болгондо, бул принцип оңой бузулушу мүмкүн . PointМисалы, координаттары бар класска xжана yаны кеңейтүү менен чекиттин түсүн кошуу керек. ColorPointБул үчүн, тиешелүү талаа менен классты жарыялоо керек болот color. Ошентип, эгерде кеңейтилген класста биз equalsата-эне ыкмасын чакырсак, ал эми ата-энеде биз координаттар гана xжана салыштырылат деп ойлосок y, анда ар кандай түстөгү, бирок координаталары бирдей болгон эки чекит бирдей деп эсептелет, бул туура эмес. Бул учурда туунду классты түстөрдү айырмалоого үйрөтүү зарыл. Бул үчүн, сиз эки ыкманы колдоно аласыз. Бирок бири симметрия эрежесин бузса , экинчиси транзиттик .
// Первый способ, нарушая симметричность
// Метод переопределен в классе ColorPoint
@Override
public boolean equals(Object o) {
    if (!(o instanceof ColorPoint)) return false;
    return super.equals(o) && ((ColorPoint) o).color == color;
}
Бул учурда, чакыруу point.equals(colorPoint)маанисин кайтарат true, ал эми салыштыруу colorPoint.equals(point)кайтып келет false, анткени "өз" классынын an objectисин күтөт. Ошентип, симметрия эрежеси бузулат. Экинчи ыкма чекиттин түсү жөнүндө маалымат жок болгон учурда "сокур" текшерүүнү камтыйт, башкача айтканда, бизде класс бар Point. Же ал жөнүндө маалымат бар болсо, түстү текшериңиз, башкача айтканда, класстын an objectисин салыштырыңыз 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;
}
Бул жерде транзитивдүүлүк принциби төмөнкүдөй бузулат. Төмөнкү an objectтердин аныктамасы бар дейли:
ColorPoint p1 = new ColorPoint(1, 2, Color.RED);
Point p2 = new Point(1, 2);
ColorPoint p3 = new ColorPoint(1, 2, Color.BLUE);
Ошентип, теңдик p1.equals(p2)жана канааттандырылган болсо да p2.equals(p3), p1.equals(p3)ал маанини кайтарат false. Ошол эле учурда, экинчи ыкма, менин оюмча, азыраак жагымдуу көрүнөт, анткени Кээ бир учурларда, алгоритм сокур болуп, салыштырууну толук аткарбай калышы мүмкүн жана сиз бул тууралуу билбешиңиз мүмкүн. Бир аз поэзия Жалпысынан, мен түшүнгөндөй, бул маселенин конкреттүү чечими жок. Кай Хорстман аттуу авторитеттүү автордун пикири бар, сиз операторду колдонууну an objectтин классын кайтарган instanceofметод чакырыгы менен алмаштырса болот getClass()жана an objectтердин өздөрүн салыштырууну баштаардан мурун алардын бир типте экендигине ынануу керек. , жана алардын жалпы келип чыгышынын фактысына көңүл бурушпайт. Ошентип, симметрия жана өтүү эрежелери канааттандырылат. Бирок ошол эле учурда баррикаданын ары жагында кеңири чөйрөдө кем эмес кадыр-барктуу дагы бир автор Жошуа Блох турат, ал бул ыкма Барбара Лисковдун алмаштыруу принцибине каршы келет деп эсептейт. Бул принципте "чалуу codeу базалык класска аны билбей туруп анын субкласстары сыяктуу мамиле кылышы керек " деп айтылат . Ал эми Хорстман сунуш кылган чечимде бул принцип ачык-айкын бузулган, анткени ал ишке ашырууга байланыштуу. Кыскасы, иштин караңгы экени көрүнүп турат. Ошондой эле белгилей кетчү нерсе, Хорстманн өзүнүн ыкмасын колдонуу эрежесин тактап, класстарды түзүүдө стратегияны чечиш керек деп ачык англис тorнде жазат жана эгерде теңдикти тестирлөө суперкласс тарабынан гана жүргүзүлө турган болсо, анда сиз муну аткаруу менен жасай аласыз. операция instanceof. Болбосо, текшерүүнүн семантикасы туунду класска жараша өзгөргөндө жана методду ишке ашыруу иерархиядан ылдый жылдыруу керек болгондо, сиз методду колдонушуңуз керек getClass(). Жошуа Блох, өз кезегинде, мурастан баш тартууну жана класска ColorPointклассты кошуу менен an objectтин курамын колдонууну сунуштайт жана пункт жөнүндө атайын маалымат алуу үчүн Pointмүмкүндүк алуу ыкмасын камсыз кылат . asPoint()Бул бардык эрежелерди бузуудан качат, бирок, менин оюмча, бул codeду түшүнүүнү кыйындатат. Үчүнчү вариант - IDEди колдонуу менен барабар ыкмасын автоматтык түрдө түзүү. Идея, демек, Horstmann муунун кайталайт, бул ыкманы суперкласста же анын урпактарында ишке ашыруу стратегиясын тандоого мүмкүндүк берет. Акыр-аягы, кийинки ырааттуулук эрежеси an objectтер өзгөрбөсө дагы x, yаларды кайра чакыруу x.equals(y)мурункудай эле маанини кайтарышы керек деп айтылат. Акыркы эреже - эч бир an object тең болбошу керек null. Бул жерде баары түшүнүктүү null- бул белгисиздик, an object белгисиздикке барабарбы? Бул түшүнүксүз, falseб.а.

Теңдикти аныктоонун жалпы алгоритми

  1. thisОбъектке шилтемелердин жана метод параметрлеринин теңдигин текшериңиз o.
    if (this == o) return true;
  2. Шилтеме аныкталганбы же oжокпу, текшериңиз null, б.а.
    Эгерде келечекте an objectтин түрлөрүн салыштырганда, оператор колдонула турган болсо instanceof, бул элементти өткөрүп жиберүүгө болот, анткени бул параметр falseбул учурда кайтып келет null instanceof Object.
  3. Оператор же ыкманы thisколдонуп an objectтин түрлөрүн салыштырыңыз , жогорудагы сүрөттөлүштү жана өзүңүздүн интуицияңызды жетекчorкке алыңыз.oinstanceofgetClass()
  4. Эгерде ыкма equalsподкласста жокко чыгарылса, сөзсүз чалыңызsuper.equals(o)
  5. Параметрдин түрүн oкеректүү класска айландырыңыз.
  6. Бардык маанилүү an object талааларын салыштыруу:
    • примитивдүү түрлөр үчүн (жанадан башка float) double, операторду колдонуу==
    • шилтеме талаалары үчүн алардын ыкмасын чакырышыңыз керекequals
    • массивдер үчүн циклдик итерацияны же ыкманы колдонсоңуз болотArrays.equals()
    • түрлөрү үчүн floatжана doubleтиешелүү ороочу класстардын салыштыруу ыкмаларын колдонуу зарыл Float.compare()жанаDouble.compare()
  7. Акырында, үч суроого жооп бериңиз: ишке ашырылган ыкма симметриялуубу ? Transitive ? макулбу ? Калган эки принцип ( рефлексивдүүлүк жана аныктык ) адатта автоматтык түрдө ишке ашырылат.

HashCode эрежелерин жокко чыгаруу

Хэш - бул кандайдыр бир убакта анын абалын сүрөттөгөн an objectтен түзүлгөн сан. Бул сан Java тorнде негизинен хэш tableларда колдонулат, мисалы HashMap. Мында an objectке негизделген санды алуунун хэш-функциясы хэш tableсы боюнча элементтердин салыштырмалуу бирдей бөлүштүрүлүшүн камсыздай тургандай ишке ашырылышы керек. Ошондой эле функция ар кандай баскычтар үчүн бирдей маанини кайтарганда кагылышуулардын ыктымалдыгын азайтуу үчүн.

Келишимдин hashCode

Хеш-функцияны ишке ашыруу үчүн тил спецификациясы төмөнкү эрежелерди аныктайт:
  • бир эле an objectте бир же бир нече жолу методду чакыруу, hashCodean objectтин маанини эсептөөгө катышкан талаалары өзгөрбөсө, ошол эле хэш маанисин кайтарышы керек.
  • эки an object боюнча методду чакыруу, hashCodeэгерде an objectтер бирдей болсо, ар дайым бирдей санды кайтарып бериши керек ( equalsбул an objectтер боюнча методду чакыруу кайтарат true).
  • эки бирдей эмес an object боюнча ыкманы чакыруу hashCodeар кандай хэш маанилерин кайтарышы керек. Бул талап милдеттүү эмес болсо да, аны ишке ашыруу хэш tableлардын иштешине оң таасирин тийгизет деп эсептеш керек.

барабар жана hashCode ыкмаларын бирге жокко чыгаруу керек

Жогоруда сүрөттөлгөн келишимдердин негизинде, codeуңуздагы ыкманы жокко чыгарганда equals, сиз ар дайым ыкманы жокко чыгарышыңыз керек hashCode. Чындыгында класстын эки инстанциясы ар кандай эс тутум аймактарында болгондуктан, алар кандайдыр бир логикалык критерийлерге ылайык салыштырылышы керек. Демек, логикалык жактан эквиваленттүү эки an object бирдей хэш маанисин кайтарышы керек. Бул ыкмалардын бирөө гана жокко чыгарылса эмне болот?
  1. equalsооба hashCodeжок

    equalsКлассыбызда методду туура аныктадык дейли жана hashCodeметодду класстагыдай калтырууну чечтик Object. Анда методдун көз карашы боюнча equalsэки an object логикалык жактан бирдей болот, ал эми методдун көз карашы боюнча hashCodeалардын эч кандай жалпылыгы болбойт. Ошентип, an objectти хэш tableга жайгаштыруу менен, биз аны ачкыч менен кайтарып албай калуу коркунучу бар.
    Мисалы, бул сыяктуу:

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

    Албетте, жайгаштырылып жаткан an object менен изделип жаткан an object логикалык жактан бирдей болгону менен эки башка an object. Бирок Себеби алардын ар кандай хэш баалуулуктары бар, анткени биз келишимди бузгандыктан, биз хэш tableнын ичегинин бир жеринде an objectибизди жоготуп алдык деп айта алабыз.

  2. hashCodeооба equalsжок.

    Эгерде биз методду жокко чыгарып hashCode, equalsкласстан методду ишке ашырууну мурастап алсак, эмне болот Object. Белгилүү болгондой, equalsдемейки ыкма жөн гана көрсөткүчтөрдү an objectтерге салыштырып, алардын бир эле an objectке тиешелүү экендигин аныктайт. Келгиле, hashCodeбиз методду бардык канондорго ылайык жаздык деп ойлойлу, тактап айтканда, аны IDE аркылуу түздүк жана ал логикалык жактан окшош an objectтер үчүн ошол эле хэш маанилерин кайтарат. Албетте, муну менен биз эки an objectти салыштыруунун кандайдыр бир механизмин аныктадык.

    Демек, мурунку абзацтагы мисал теориялык жактан аткарылышы керек. Бирок биз дагы эле хэш tableсында an objectибизди таба албайбыз. Биз буга жакын болобуз, анткени, жок дегенде, биз an object жата турган таштанды үстөл себетин табабыз.

    Хеш-tableда an objectти ийгorктүү издөө үчүн, ачкычтын хэш маанилерин салыштыруудан тышкары, изделген an object менен ачкычтын логикалык теңдигин аныктоо да колдонулат. Башкача айтканда, equalsыкманы жокко чыгарбай туруп, эч кандай жол жок.

hashCode аныктоонун жалпы алгоритми

Бул жерде, менимче, сиз көп кабатыр болбоңуз жана сүйүктүү IDEиңизде ыкманы жаратыңыз. Анткени алтын катышты издөө үчүн биттердин оңго жана солго жылышы, б.а. нормалдуу бөлүштүрүү - бул толугу менен өжөр жигиттер үчүн. Жеке мен бир эле Идеяга караганда жакшыраак жана тезирээк жасай алам деп күмөн санайм.

Корутундунун ордуна

Ошентип, биз методдор Java тorнде так аныкталган ролду equalsойноорун жана эки an objectтин логикалык теңдигин алуу үчүн түзүлгөнүн көрөбүз. hashCodeМетод болгон учурда, equalsбул an objectтерди салыштырууга түздөн-түз байланышы бар, hashCodeкыйыр учурда, зарыл болгон учурда, айталы, хэш tableларында же окшош маалымат структураларында an objectтин болжолдуу жайгашкан жерин аныктоо үчүн an objectти издөө ылдамдыгын жогорулатуу. Келишимдерден тышкары , an objectтерди салыштырууга байланыштуу дагы бир талап бар equals. hashCodeБул compareToинтерфейс ыкмасынын Comparableырааттуулугу equals. x.equals(y) == trueБул талап иштеп чыгуучуну ар дайым качан кайтып келүүгө милдеттендирет x.compareTo(y) == 0. Башкача айтканда, биз эки an objectтин логикалык салыштырылышы өтүнмөнүн эч бир жеринде карама-каршы келбеши керек жана дайыма ырааттуу болушу керек экенин көрөбүз.

Булактар

Натыйжалуу Java, Экинчи Басылышы. Джошуа Блох. Абдан жакшы китепти бекер которуу. Java, кесипкөй китепкана. 1-том. Негиздер. Кей Хорстман. Бир аз азыраак теория жана көбүрөөк практика. Бирок баары Блохтукундай майда-чүйдөсүнө чейин талданган эмес. Ошол эле барабар () боюнча көз караш бар болсо да. Сүрөттөрдөгү маалымат структуралары. HashMap Java тorндеги HashMap түзмөгүндөгү абдан пайдалуу макала. Булактарды карагандын ордуна.
Комментарийлер
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION