1. Сравняване на обекти в Java

В Java обектите могат да се сравняват Howто по препратка, така и по стойност.

Сравняване на референции

Ако две променливи сочат към един и същ обект в паметта, тогава препратките, съхранени в тези променливи, са равни. Ако сравните тези променливи с помощта на оператора за equalsство ( ==), получавате true и този резултат има смисъл. Тук всичко е просто.

Код Конзолен изход
Integer a = 5;
Integer b = a;
System.out.println(a == b);


true

Сравнение по стойност

Но често можете да срещнете ситуации, при които две променливи се отнасят до два различни обекта, които са идентични. Например два различни низови обекта, които съдържат един и същи текст.

За да определите дали различните обекти са идентични, използвайте equals()метода. Например:

Код Конзолен изход
String a = new String("Hello");
String b = new String("Hello");
System.out.println(a == b);
System.out.println(a.equals(b));


false
true

Методът equalsне е ограничен до Stringкласа. Всеки клас го има.

Дори класове, които пишете сами, и ето защо.



2. Objectклас

Всички класове в Java наследяват Objectкласа. Създателите на Java измислиха този подход.

И ако един клас наследи Objectкласа, тогава той получава всички методи на Objectкласа. И това е основна последица от наследството.

С други думи, всеки клас има методите на Objectкласа, дори ако техният code не ги споменава.

Тези наследени методи включват методи, свързани със сравнение на обекти. Това са методите equals()и hashCode().

Код В действителност, ето Howво ще имаме:
class Person
{
   String name;
   int age;
}
class Person extends Object
{
   String name;
   int age;

   public boolean equals(Object obj)
   {
      return this == obj;
   }

   public int hashCode()
   {
      return address_of_object_in_memory; // This is the default implementation, but there may be a different implementation
   }
}

В примера по-горе създадохме прост Personклас с параметри за име и възраст, но не и един метод. Но тъй като всички класове наследяват Objectкласа, Personкласът автоматично има два метода:

Метод Описание
boolean equals(Object obj)
Сравнява текущия обект и преминалия обект
int hashCode()
Връща хеш codeа на текущия обект

Оказва се, че абсолютно всеки обект има equalsметод и обекти от различни типове могат да се сравняват помежду си. Такъв code ще се компorра и ще работи перфектно.

Код Конзолен изход
Integer a = 5;
String s = "Hello";
System.out.println(a.equals(s));
System.out.println(s.equals(a));


false
false
Object a = new Integer(5);
Object b = new Integer(5);
System.out.println(a.equals(b)) ;


true

3. equals()метод

Методът equals(), наследен от Objectкласа, прилага най-простия алгоритъм за сравняване на текущия обект с преминалите обекти: той просто сравнява препратките към обектите.

Получавате същия резултат, ако просто сравнявате Personпроменливи, instead of да извиквате equals()метода. Пример:

Код Конзолен изход
Person a = new Person();
a.name = "Steve";

Person b = new Person();
b.name = "Steve";

System.out.println(a == b);
System.out.println(a.equals(b));






false
false

Когато equalsметодът се извика на a, той просто сравнява препратката, съхранена в aпроменливата, с препратката, съхранена в bпроменливата.

Сравнението обаче работи по различен начин за Stringкласа. Защо?

Защото хората, които са създали Stringкласа, са написали своя собствена реализация на equals()метода.

Реализация на equals()метода

Сега нека напишем нашата собствена реализация на метода equals в Personкласа. Ще разгледаме 4 основни случая.

Важно:
Независимо кой клас заменя equalsметода, той винаги приема Objectобект като аргумент

Сценарий 1 : същият обект, на който equalsсе извиква методът, също се предава на equalsметода. Ако препратките на текущия обект и предадения обект са равни, методът трябва да върне true. Един обект е equals на себе си.

В codeа ще изглежда така:

Код Описание
public boolean equals(Object obj)
{
   if (this == obj)
    return true;

   // The rest of the code of the equals method
}


Сравнете препратките

Сценарий 2 : nullпредава се на equalsметода — нямаме с Howво да сравняваме. Обектът, на който equalsсе извиква методът, определено не е null, така че трябва да се върнем falseв този случай.

В codeа ще изглежда така:

Код Описание
public boolean equals(Object obj)
{
   if (this == obj)
      return true;

   if (obj == null)
      return false;

   // The rest of the code of the equals method
}


Сравнете препратките


Предаденият обект ли е null?

Сценарий 3Person : на метода се предава препратка към обект, който не е a equals. Обектът equals ли е Personна непредмета Person? Това е въпрос, който разработчикът на Personкласа трябва да реши, Howто той or тя иска.

Но обикновено обектите трябва да са от един и същи клас, за да се считат за равни. Следователно, ако нещо различно от обект от Personкласа се предаде на нашия метод equals, тогава ние винаги ще връщаме false. Как можете да проверите вида на обект? Точно така — с помощта на instanceofоператора.

Ето How изглежда нашият нов code:

Код Описание
public boolean equals(Object obj)
{
   if (this == obj)
      return true;

   if (obj == null)
      return false;

   if (!(obj instanceof Person))
      return false;

   // The rest of the code of the equals method
}


Сравнете препратките


Предаденият обект ли е null?


Ако предаденият обект не е aPerson

4. Сравнение на два Personобекта

Какво завършихме? Ако сме стигнали до края на метода, тогава имаме Personпрепратка към обект, която не е null. Затова го преобразуваме в a Personи сравняваме съответните вътрешни данни на двата обекта. И това е нашият четвърти сценарий .

Код Описание
public boolean equals(Object obj)
{
   if (this == obj)
      return true;

   if (obj == null)
      return false;

   if (!(obj instanceof Person))
      return false;

   Person person = (Person) obj;

   // The rest of the code of the equals method
}


Сравнете препратките


Предаденият обект ли е null?


Ако предаваният обект не е Person


Typecasting

И How сравнявате два Personобекта? Те са равни, ако имат едно и също име ( name) и възраст ( age). Крайният code ще изглежда така:

Код Описание
public boolean equals(Object obj)
{
   if (this == obj)
      return true;

   if (obj == null)
      return false;

   if (!(obj instanceof Person))
      return false;

   Person person = (Person) obj;

   return this.name == person.name && this.age == person.age;
}


Сравнете препратките


Предаденият обект ли е null?


Ако предаваният обект не е Person


Typecasting

Но това не е всичко.

Първо, полето за име е String, така че трябва да сравните полето за име, като извикате equalsметода.

this.name.equals(person.name)

Второ, nameполето може да бъде null: в този случай не можете да equalsго използвате. Имате нужда от допълнителна проверка за null:

this.name != null && this.name.equals(person.name)

Въпреки това, ако полето за име е nullи в двата Personобекта, тогава имената все още са равни.

Кодът за четвъртия сценарий може да изглежда така:

Person person = (Person) obj;

if (this.age != person.age)
   return false;

if (this.name == null)
   return person.name == null;

return this.name.equals(person.name);


Ако възрастите не са равни,
веднага return false

Ако this.nameе равно на null, няма смисъл да се сравнява с помощта на equalsметода. Тук or второто nameполе е равно на null, or не е.

Сравнете двете полета за имена, като използвате equalsметода.


5. hashCode()метод

В допълнение към equalsметода, който е предназначен да извърши подробно сравнение на всички полета на двата обекта, има друг метод, който може да се използва за неточно, но много бързо сравнение: hashCode().

Представете си, че подреждате по азбучен ред списък от хиляди думи и трябва многократно да сравнявате двойки думи. И думите са дълги, състоящи се от много букви. Най-общо казано, подобно сравнение ще отнеме много време.

Но може да се ускори. Да предположим, че имаме думи, които започват с различни букви - веднага става ясно, че са различни. Но ако започват с едни и същи букви, тогава все още не можем да кажем Howъв ще бъде резултатът: думите може да се окажат еднакви or различни.

Методът hashCode()работи на подобен принцип. Ако го извикате на обект, той връща няHowво число — аналогично на първата буква в думата. Това число има следните свойства:

  • Идентични обекти винаги имат един и същ хешcode
  • Различните обекти могат да имат един и същ хешcode or техните хешcodeове могат да бъдат различни
  • Ако обектите имат различни хешcodeове, тогава обектите определено са различни

За да направим това още по-ясно, нека преформулираме тези свойства по отношение на думи:

  • Еднаквите думи винаги имат еднакви първи букви.
  • Различните думи могат да имат еднакви първи букви or техните първи букви могат да бъдат различни
  • Ако думите имат различни първи букви, то думите определено са различни

Последното свойство се използва за ускоряване на сравнението на обекти:

Първо се изчисляват хеш codeовете на двата обекта. Ако тези хешcodeове са различни, тогава обектите определено са различни и няма нужда да ги сравнявате допълнително.

Но ако хешcodeовете са еднакви, тогава все пак трябва да сравним обектите с помощта на метода equals.



6. Договори в code

Поведението, описано по-горе, трябва да се изпълнява от всички класове в Java. По време на компилация няма начин да се провери дали обектите са сравнени правилно.

Java програмистите имат универсално споразумение, че ако напишат своя собствена реализация на метода equals() и по този начин заменят стандартната имплементация (в класа Object), те също трябва да напишат своя собствена реализация на hashCode()метода по такъв начин, че гореспоменатите правила да са удовлетворен.

Това споразумение се нарича договор .

Ако внедрите само equals()or само hashCode()метода във вашия клас, тогава вие сте в грубо нарушение на договора (нарушor сте споразумението). не прави това

Ако други програмисти използват вашия code, той може да не работи правилно. Нещо повече, ще използвате code, който разчита на спазването на горните договори.

важно!

Когато търсите елемент, всички колекции на Java първо сравняват хешcodeовете на обектите и едва след това извършват сравнение с помощта на equalsметода.

Това означава, че ако дадете на собствения си клас equalsметод, но не напишете свой собствен hashCode()метод or го внедрите неправилно, тогава колекциите може да не работят правилно с вашите обекти.

Например, можете да добавите обект към списък и след това да го търсите с помощта на contains()метода, но колекцията може да не намери вашия обект.