JavaRush /Java блогу /Random-KY /барабар & hashCode ыкмалары: колдонуу практикасы

барабар & hashCode ыкмалары: колдонуу практикасы

Группада жарыяланган
Салам! Бүгүн биз Java эки маанилүү ыкмалары жөнүндө сүйлөшөбүз - equals()жана hashCode(). Бул биз алар менен биринчи жолу жолуккан жокпуз: JavaRush курсунун башында бул тууралуу кыскача лекцияequals() бар болчу - эгер сиз аны унутуп калган болсоңуз же мурда көрө элек болсоңуз, аны окуп чыгыңыз. Методдор &  hashCode: колдонуу практикасы - 1Бүгүнкү сабакта биз бул түшүнүктөр жөнүндө майда-чүйдөсүнө чейин сүйлөшөбүз - мага ишен, сүйлөшө турган көп нерсе бар! Жана жаңы нерсеге өтүүдөн мурун, биз буга чейин өткөн нерселер боюнча эс тутумубузду жаңырталы :) Эсиңерде болгондой, эки an objectти “ ==” оператору аркылуу кадимкидей салыштыруу жаман идея, анткени “ ==” шилтемелерди салыштырат. Бул жерде биздин акыркы лекциядагы унаалар менен мисал:
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);
   }
}
Консолдук чыгаруу:

false
Биз класстын эки окшош an objectисин түздүк окшойт Car: эки машинадагы бардык талаалар бирдей, бирок салыштыруунун натыйжасы дагы эле жалган. Биз буга чейин эле себебин билебиз: шилтемелер car1жана car2эстутумдагы ар кандай даректерди көрсөтүп турат, ошондуктан алар бирдей эмес. Биз дагы эле эки шилтемени эмес, эки an objectти салыштыргыбыз келет. Объекттерди салыштыруу үчүн эң жакшы чечим болуп саналат equals().

барабар() ыкмасы

Эсиңизде болсо керек, биз бул ыкманы нөлдөн баштап түзбөйбүз, бирок аны жокко чыгарабыз - акыры, метод equals()класста аныкталган Object. Бирок, анын кадимки түрүндө ал аз колдонулат:
public boolean equals(Object obj) {
   return (this == obj);
}
equals()Класста метод ушундайча аныкталат Object. Ошол эле шилтемелерди салыштыруу. Ал эмне үчүн минтип жаралган? Кана, тилди жаратуучулар сиздин программаңыздагы кайсы an objectилер бирдей болуп, кайсынысы тең эмес экенин кайдан бorшет? :) Бул методдун негизги идеясы equals()- класстын жаратуучусу бул класстын an objectилеринин бирдейлиги текшериле турган мүнөздөмөлөрдү өзү аныктайт. Муну менен сиз equals()классыңыздагы ыкманы жокко чыгарасыз. Эгер сиз "мүнөздөмөлөрдү өзүңүз аныктайсыз" дегендин маанисин толук түшүнбөсөңүз, анда бир мисалды карап көрөлү. Бул жерде адамдын жөнөкөй классы - 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.
}
Келгиле, биз программа жазып жатабыз дейли, ал эки адамдын эгиздер менен байланышы бар же жөн эле кошуналар экенин аныктоо керек. Бизде беш өзгөчөлүк бар: мурундун көлөмү, көздүн түсү, чач жасалгасы, тырыктардын болушу жана ДНК биологиялык тестинин жыйынтыгы (жөнөкөйлүк үчүн - codeдук номер түрүндө). Бул өзгөчөлүктөрдүн кайсынысы биздин программа эгиз туугандарды аныктоого мүмкүндүк берет деп ойлойсуз? Методдор &  hashCode: колдонуу практикасы - 2Албетте, биологиялык тест гана кепилдик бере алат. Эки адамдын көзүнүн түсү, чач жасалгасы, мурду, жада калса тырыктары бирдей болушу мүмкүн – дүйнөдө көп адамдар бар, кокустуктардан качуу мүмкүн эмес. Бизге ишенимдүү механизм керек: ДНК тестинин жыйынтыгы гана так жыйынтык чыгарууга мүмкүндүк берет. Бул биздин ыкма үчүн эмнени билдирет equals()? ManАны программабыздын талаптарын эске алуу менен класста кайра аныкташыбыз керек . Метод эки an objectтин талаасын салыштырышы керек int dnaCode, эгерде алар бирдей болсо, анда an objectтер бирдей болот.
@Override
public boolean equals(Object o) {
   Man man = (Man) o;
   return dnaCode == man.dnaCode;
}
бул чын эле ушунчалык жөнөкөй? Жок эле. Биз бир нерсени сагындык. Бул учурда, биздин an objectилер үчүн биз алардын теңдиги орнотулган бир гана "маанилүү" талааны аныктадык - dnaCode. Эми элестетип көргүлө, бизде 1 эмес, 50 ​​"маанилүү" талаа болмок.Эгерде эки an objectтин 50 талаасынын баары бирдей болсо, анда an objectтер бирдей болот. Бул да болушу мүмкүн. Негизги маселе – 50 талаанын теңдигин эсептөө көп убакытты жана ресурсту талап кылган процесс. Эми элестетип көрүңүз, класска кошумча Manбизде . Жана башка программист сиздин класстарыңызды колдонсо, ал өзүнүн программасына төмөнкүдөй нерсени оңой эле жаза алат: 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));
}
Бул учурда талаанын маанилерин текшерүүнүн эч кандай мааниси жок: биз эки башка класстагы an objectтерди карап жатканыбызды көрүп жатабыз жана алар принципиалдуу түрдө бирдей боло алbyte! equals()Бул эки бирдей класстагы an objectтерди салыштыруу ыкмасына чек коюу керек дегенди билдирет . Муну ойлогонубуз жакшы!
@Override
public boolean equals(Object o) {
   if (getClass() != o.getClass()) return false;
   Man man = (Man) o;
   return dnaCode == man.dnaCode;
}
Бирок, балким, биз дагы бир нерсени унутуп калгандырбыз? Ммм... Жок дегенде биз an objectти өзү менен салыштырбай жатканыбызды текшеришибиз керек! Эгерде А жана В шилтемелери эстутумдагы бир эле даректи көрсөтсө, анда алар бир эле an object жана 50 талааны салыштырып убакытты коротуунун кереги жок.
@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;
}
Кошумчалай кетсек, үчүн чекти кошуу зыяны тийбейт null: эч бир an object ге барабар боло алbyte null, бул учурда кошумча текшерүүлөрдүн мааниси жок. Мунун баарын эске алып, биздин equals()класстык методубуз 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;
}
Биз жогоруда айтылган бардык алгачкы текшерүүлөрдү жүргүзөбүз. Эгер бул чыкса:
  • биз бир класстын эки an objectисин салыштырабыз
  • бул бир эле an object эмес
  • биз an objectибизди салыштырып жаткан жокпузnull
...андан кийин биз олуттуу мүнөздөмөлөрдү салыштырууга өтөбүз. Биздин учурда dnaCodeэки an objectтин талаалары. Методду жокко чыгарууда equals(), бул талаптарды сактоону унутпаңыз:
  1. Рефлексивдүүлүк.

    equals()Ар кандай an object өзүнө болушу керек .
    Бул талапты биз буга чейин эске алганбыз. Биздин ыкма мындай дейт:

    if (this == o) return true;

  2. Симметрия.

    Эгерде a.equals(b) == true, анда b.equals(a)ал кайтып келиши керек true.
    Биздин ыкма да ушул талапка жооп берет.

  3. Өтмөлүк.

    Эгерде эки an object кандайдыр бир үчүнчү an objectке барабар болсо, анда алар бири-бирине барабар болушу керек.
    Эгерде a.equals(b) == trueжана болсо a.equals(c) == true, анда текшерүү b.equals(c)да чындыкты кайтарышы керек.

  4. Туруктуулук.

    Иштин жыйынтыгы equals()ага кирген талаалар өзгөргөндө гана өзгөрүшү керек. Эгерде эки an objectтин маалыматтары өзгөрбөсө, текшерүүнүн жыйынтыгы equals()дайыма бирдей болушу керек.

  5. менен барабарсыздык null.

    Кандайдыр бир an object үчүн чек " a.equals(null)false" кайтарып бериши керек.Бул
    жөн гана "пайдалуу сунуштардын" жыйындысы эмес, Oracle documentтеринде жазылган ыкмалардын катуу келишими.

hashCode() ыкмасы

Эми ыкма жөнүндө сүйлөшөлү hashCode(). Бул эмне үчүн керек? Дал ушул максатта - an objectтерди салыштыруу. Бирок бизде буга чейин бар equals()! Эмне үчүн башка ыкма? Жооп жөнөкөй: өндүрүмдүүлүгүн жогорулатуу. Java'да , методу менен көрсөтүлгөн хэш-функция hashCode()ар кандай an object үчүн туруктуу узундуктагы сандык маанини кайтарат. Java учурда, ыкма hashCode()32 биттик санды кайтарат int. Эки санды бири-бири менен салыштыруу ыкмасын колдонуу менен эки an objectти салыштырууга караганда алда канча тезирээк equals(), айрыкча көп талааларды колдонсо. Эгерде биздин программа an objectтерди салыштыра турган болсо, анда хэш-code боюнча муну жасоо бир топ жеңил болот жана алар бирдей болгондо гана hashCode()- менен салыштырууга өтүңүз equals(). Айтмакчы, хэш-негизделген маалымат структуралары кандайча иштейт — мисалы, сиз билесиз HashMap! Метод hashCode(), сыяктуу эле equals(), иштеп чыгуучунун өзү тарабынан жокко чыгарылат. Жана сыяктуу эле equals(), ыкма hashCode()Oracle documentтеринде көрсөтүлгөн расмий талаптарга ээ:
  1. Эгерде эки an object бирдей болсо (башкача айтканда, ыкма equals()чындыкты кайтарат), аларда бирдей хэш-code болушу керек.

    Болбосо биздин ыкмалар маанисиз болуп калат. Жогоруда айтылгандай, текшерүү hashCode()биринчи орунда иштеши керек. Эгерде хэш codeдору ар кандай болсо, текшерүү an objectтер чындыгында бирдей болгонуна карабастан, жалганды кайтарат (биз методдо аныкталгандай equals()).

  2. Эгерде метод hashCode()бир эле an objectте бир нече жолу чакырылса, ал ар бир жолу бирдей санды кайтарып бериши керек.

  3. 1-эреже тескери иштебейт. Эки башка an object бир эле хэш-codeго ээ болушу мүмкүн.

Үчүнчү эреже бир аз түшүнүксүз. Бул кантип болушу мүмкүн? Түшүндүрүү абдан жөнөкөй. Метод hashCode()кайтып келет int. int32 биттик сан болуп саналат. Анын чектелген саны бар - -2,147,483,648ден +2,147,483,647ге чейин. Башкача айтканда, сандын 4 миллиарддан бир аз ашык варианты бар int. Эми сиз жер бетиндеги бардык тирүү адамдар жөнүндө маалыматтарды сактоо үчүн программа түзүп жатканыңызды элестетиңиз. Ар бир адамдын өзүнүн класстык an objectи болот Man. Жер бетинде ~7,5 миллиард адам жашайт. Башкача айтканда, an objectтерди сандарга айландыруу үчүн канчалык жакшы алгоритм Manжазсак да, бизде сандар жетишсиз болот. Бизде болгону 4,5 миллиард вариант жана андан көп адамдар бар. Бул канчалык аракет кылбайлы, хэш codeдору ар кандай адамдар үчүн бирдей болот дегенди билдирет. Бул жагдай (эки башка an objectинин хэш codeдору дал келген) кагылышуу деп аталат. Методду жокко чыгарууда программисттин максаттарынын бири hashCode()- мүмкүн болушунча кагылышуулардын потенциалдуу санын азайтуу. Бардык ушул эрежелерди эске алуу менен биздин hashCode()класс үчүн методубуз кандай болот? ManБул сыяктуу:
@Override
public int hashCode() {
   return dnaCode;
}
Таң калдыңызбы? :) Күтүлбөгөн жерден, бирок талаптарды карап көрсөңүз, биз бардыгына баш ийип жатканыбызды көрөсүз. Биздики equals()чындыкты кайтарган an objectтер менен бирдей болот hashCode(). Эгерде биздин эки an objectибиз Manбирдей мааниге ээ болсо equals(башкача айтканда, алар бирдей мааниге ээ dnaCode), биздин методубуз бирдей санды кайтарат. Келгиле, бир кыйла татаал мисалды карап көрөлү. Биздин программа коллекционерлер үчүн кымбат баалуу унааларды тандоо керек дейли. Коллекция татаал нерсе жана анын көптөгөн өзгөчөлүктөрү бар. 1963-жылдагы машинанын баасы 1964-жылдагы ошол эле машинага караганда 100 эсе кымбат болот. 1970-жылкы кызыл унаа ошол эле жылдагы көк унаадан 100 эсе кымбат болот. Методдор &  hashCode: колдонуу практикасы - 4Биринчи учурда, класс менен Man, биз талаалардын көбүн (б.а., адамдын мүнөздөмөлөрү) маанисиз деп жокко чыгарып, салыштыруу үчүн талааны гана колдондук dnaCode. Бул жерде биз абдан уникалдуу сфера менен иштеп жатабыз жана анча маанилүү эмес деталдар болушу мүмкүн эмес! Бул жерде биздин класс 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.
}
Бул жерде, салыштырганда, биз бардык талааларды эске алышыбыз керек. Ар кандай ката кардар үчүн жүз миңдеген доллар чыгым болушу мүмкүн, андыктан коопсуз болгонуңуз жакшы:
@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);
}
Биздин ыкмабызда equals()биз мурда айткан бардык текшерүүлөрдү унуткан жокпуз. Бирок азыр биз an objectилерибиздин үч талаасынын ар бирин салыштырып көрөбүз. Бул программада теңдик абсолюттук, ар бир тармакта болушу керек. Эмне жөнүндө hashCode?
@Override
public int hashCode() {
   int result = model == null ? 0 : model.hashCode();
   result = result + manufactureYear;
   result = result + dollarPrice;
   return result;
}
Биздин класстагы талаа model- жип. Бул ыңгайлуу: Stringметод hashCode()класста мурунтан эле жокко чыгарылган. Биз талаанын хэш codeун эсептеп model, ага калган эки сандык талаанын суммасын кошобуз. Java тorнде кагылышуулардын санын азайтуу үчүн колдонулган бир аз трюк бар: хэш-codeду эсептөөдө ортодогу натыйжаны так санга көбөйтүңүз. Эң көп колдонулган сан 29 же 31. Биз азыр математиканын майда-чүйдөсүнө чейин кирбейбиз, бирок келечектеги маалымат үчүн, ортодогу жыйынтыктарды жетишерлик чоң так санга көбөйтүү хэштин натыйжаларын “жайып салууга” жардам берерин унутпаңыз. функциясы менен аяктайт жана ошол эле хэшcode менен азыраак an objectтерге ээ болот. LuxuryAuto биздин методубуз үчүн hashCode()ал төмөнкүдөй болот:
@Override
public int hashCode() {
   int result = model == null ? 0 : model.hashCode();
   result = 31 * result + manufactureYear;
   result = 31 * result + dollarPrice;
   return result;
}
Бул механизмдин бардык татаалдыктары жөнүндө көбүрөөк маалыматты StackOverflow сайтындагы бул посттон , ошондой эле Джошуа Блохтун " Натыйжалуу Java " китебинен окуй аласыз . Акырында айта кете турган дагы бир маанилүү жагдай бар. Ар бир жолу жокко чыгарууда equals(), hashCode()биз бул ыкмаларда эске алынган an objectтин белгилүү бир талааларын тандап алдык. equals()Бирок биз жана ар кандай талааларды эске ала алабызбы hashCode()? Техникалык жактан биз алабыз. Бирок бул жаман идея жана бул жерде эмне үчүн:
@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;
}
Бул жерде LuxuryAuto классы equals()үчүн биздин ыкмаларыбыз. hashCode()Метод hashCode()өзгөрүүсүз калды жана equals()биз талааны ыкмадан алып салдык model. Эми модель эки an objectти салыштыруу үчүн мүнөздөмө эмес equals(). Бирок хэш-codeду эсептөөдө дагы эле эске алынат. Натыйжада биз эмне алабыз? Келгиле, эки машина түзүп, аны текшерип көрөлү!
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
Ката! үчүн ар кандай талааларды колдонуу менен equals()жана hashCode()биз алар үчүн түзүлгөн келишимди бузганбыз! Эки бирдей equals()an objectтин хэш codeу бирдей болушу керек. Биз алар үчүн ар кандай маанилерди алдык. Мындай каталар, өзгөчө, хэштерди колдонгон коллекциялар менен иштөөдө укмуштуудай кесепеттерге алып келиши мүмкүн. Ошондуктан, кайра аныктоодо equals()жана hashCode()ошол эле талааларды колдонуу туура болот. Лекция абдан узун болду, бирок бүгүн сиз көп жаңы нерселерди үйрөндүңүз! :) Көйгөйлөрдү чечүүгө кайра кайтууга убакыт келди!
Комментарийлер
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION