1. Jämföra objekt i Java

I Java kan objekt jämföras både med referens och värde.

Jämför referenser

Om två variabler pekar på samma objekt i minnet är referenserna som lagras i dessa variabler lika. Om du jämför dessa variabler med hjälp av likhetsoperatorn ( ==), får du sanning, och det resultatet är vettigt. Allt är enkelt här.

Koda Konsolutgång
Integer a = 5;
Integer b = a;
System.out.println(a == b);


true

Jämföra efter värde

Men du kan ofta stöta på situationer där två variabler refererar till två distinkta objekt som är identiska. Till exempel två olika strängobjekt som innehåller samma text.

För att avgöra om olika objekt är identiska, använd equals()metoden. Till exempel:

Koda Konsolutgång
String a = new String("Hello");
String b = new String("Hello");
System.out.println(a == b);
System.out.println(a.equals(b));


false
true

Metoden equalsär inte begränsad till Stringklassen. Varje klass har det.

Även klasser som du skriver på egen hand, och här är varför.



2. Objectklass

Alla klasser i Java ärver Objectklassen. Javas skapare kom på detta tillvägagångssätt.

Och om en klass ärver Objectklassen, får den alla klassens metoder Object. Och detta är en stor konsekvens av arv.

Med andra ord, varje klass har klassens metoder Object, även om deras kod inte nämner dem.

Dessa ärvda metoder inkluderar metoder relaterade till objektjämförelse. Dessa är equals()och hashCode()metoderna.

Koda I verkligheten, här är vad vi kommer att ha:
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
   }
}

I exemplet ovan skapade vi en enkel Personklass med namn- och åldersparametrar, men inte en enda metod. Men eftersom alla klasser ärver Objectklassen Personhar klassen automatiskt två metoder:

Metod Beskrivning
boolean equals(Object obj)
Jämför det aktuella objektet och det skickade objektet
int hashCode()
Returnerar hashkoden för det aktuella objektet

Det visar sig att absolut varje objekt har equalsmetoden, och objekt av olika typer kan jämföras med varandra. Sådan kod kommer att kompilera och fungera perfekt.

Koda Konsolutgång
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()metod

Metoden equals(), ärvd från Objectklassen, implementerar den enklaste algoritmen för att jämföra det aktuella objektet med skickade objekt: den jämför bara referenser till objekten.

Du får samma resultat om du bara jämför Personvariabler istället för att anropa equals()metoden. Exempel:

Koda Konsolutgång
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

När equalsmetoden anropas ajämför den helt enkelt referensen lagrad i avariabeln med referensen lagrad i variabeln b.

Jämförelse fungerar dock annorlunda för Stringklassen. Varför?

Eftersom personerna som skapade Stringklassen skrev sin egen implementering av equals()metoden.

Implementering av equals()metoden

Låt oss nu skriva vår egen implementering av equals-metoden i Personklassen. Vi kommer att överväga 4 huvudfall.

Viktig:
Oavsett vilken klass som åsidosätter equalsmetoden tar den alltid ett Objectobjekt som argument

Scenario 1 : samma objekt som equalsmetoden anropas på skickas också till equalsmetoden. Om referenserna för det aktuella objektet och det skickade objektet är lika, måste metoden returnera true. Ett föremål är lika med sig självt.

I koden kommer det se ut så här:

Koda Beskrivning
public boolean equals(Object obj)
{
   if (this == obj)
    return true;

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


Jämför referenser

Scenario 2 : nullöverförs till equalsmetoden — vi har inget att jämföra med. Objektet som equalsmetoden anropas på är definitivt inte null, så vi måste återkomma falsei det här fallet.

I koden kommer det se ut så här:

Koda Beskrivning
public boolean equals(Object obj)
{
   if (this == obj)
      return true;

   if (obj == null)
      return false;

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


Jämför referenser


Är det skickade objektet null?

Scenario 3 : en referens till ett objekt som inte är ett Personskickas till equalsmetoden. Är Personobjektet lika med icke- Personobjektet? Det är en fråga för klassens utvecklare Personatt bestämma hur han eller hon vill.

Men vanligtvis måste objekt vara av samma klass för att anses lika. Därför, om något annat än ett objekt i Personklassen skickas till vår equals-metod, kommer vi alltid att returnera false. Hur kan du kontrollera typen av ett objekt? Det stämmer — genom att använda instanceofoperatören.

Så här ser vår nya kod ut:

Koda Beskrivning
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
}


Jämför referenser


Är det skickade objektet null?


Om det passerade objektet inte är enPerson

4. Jämföra två Personobjekt

Vad slutade vi med? Om vi ​​har nått slutet av metoden har vi en Personobjektreferens som inte är null. Så vi konverterar det till a Personoch jämför relevanta interna data för båda objekten. Och det är vårt fjärde scenario .

Koda Beskrivning
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
}


Jämför referenser


Är det skickade objektet null?


Om det skickade objektet inte är en Person


Typecasting

Och hur jämför man två Personobjekt? De är lika om de har samma namn ( name) och ålder ( age). Den slutliga koden kommer att se ut så här:

Koda Beskrivning
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;
}


Jämför referenser


Är det skickade objektet null?


Om det skickade objektet inte är en Person


Typecasting

Men det är inte allt.

Först är namnfältet en String, så du måste jämföra namnfältet genom att anropa equalsmetoden.

this.name.equals(person.name)

För det andra namekan fältet vara null: i så fall kan du inte använda equalsdet. Du behöver en extra check för null:

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

Som sagt, om namnfältet finns nulli båda Personobjekten är namnen fortfarande lika.

Koden för det fjärde scenariot kan se ut så här:

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);


Om åldrarna inte är lika,
omedelbart return false

If this.nameär lika med null, är det ingen idé att jämföra med equalsmetoden. Här är antingen det andra namefältet lika med null, eller så är det inte.

Jämför de två namnfälten med equalsmetoden.


5. hashCode()metod

Utöver equalsmetoden, som är avsedd att utföra en detaljerad jämförelse av alla fält för båda objekten, finns det en annan metod som kan användas för en oprecis men mycket snabb jämförelse: hashCode().

Föreställ dig att du sorterar alfabetiskt en lista med tusentals ord, och du måste upprepade gånger jämföra ordpar. Och orden är långa och består av massor av bokstäver. Generellt sett skulle en sådan jämförelse ta väldigt lång tid.

Men det går att påskynda. Anta att vi har ord som börjar med olika bokstäver — det är direkt tydligt att de är olika. Men om de börjar med samma bokstäver kan vi ännu inte säga vad resultatet blir: orden kan visa sig vara lika eller olika.

Metoden hashCode()fungerar enligt en liknande princip. Om du anropar det på ett objekt, returnerar det någon siffra - analogt med den första bokstaven i ett ord. Detta nummer har följande egenskaper:

  • Identiska objekt har alltid samma hashkod
  • Olika objekt kan ha samma hashkod, eller så kan deras hashkoder vara olika
  • Om objekt har olika hashkoder, är objekten definitivt olika

För att göra detta ännu tydligare, låt oss omformulera dessa egenskaper i termer av ord:

  • Identiska ord har alltid samma första bokstäver.
  • Olika ord kan ha samma första bokstäver, eller deras första bokstäver kan vara olika
  • Om ord har olika första bokstäver, så är orden definitivt olika

Den sista egenskapen används för att påskynda jämförelsen av objekt:

Först beräknas hashkoderna för de två objekten. Om dessa hashkoder är olika, är objekten definitivt olika, och det finns ingen anledning att jämföra dem ytterligare.

Men om hashkoderna är desamma måste vi fortfarande jämföra objekten med metoden equals.



6. Kontrakt i kod

Beteendet som beskrivs ovan måste implementeras av alla klasser i Java. Under kompileringen finns det inget sätt att kontrollera om objekt jämförs korrekt.

Java-programmerare har en universell överenskommelse om att om de skriver sin egen implementering av metoden equals() och därigenom åsidosätter standardimplementeringen (i klassen), så Objectmåste de också skriva sin egen implementering av hashCode()metoden på ett sådant sätt att de ovan nämnda reglerna är nöjd.

Detta arrangemang kallas ett kontrakt .

Om du implementerar endast metoden equals()eller bara hashCode()metoden i din klass, bryter du grovt mot avtalet (du har brutit mot avtalet). Gör inte det här.

Om andra programmerare använder din kod kanske den inte fungerar korrekt. Dessutom kommer du att använda kod som är beroende av att ovanstående avtal följs.

Viktig!

När du söker efter ett element jämför alla Java-samlingar först objektens hashkoder och utför först sedan en jämförelse med metoden equals.

Det betyder att om du ger din egen klass en equalsmetod men du inte skriver din egen hashCode()metod eller om du implementerar den felaktigt, kanske samlingar inte fungerar korrekt med dina objekt.

Du kan till exempel lägga till ett objekt i en lista och sedan söka efter det med metoden, contains()men samlingen kanske inte hittar ditt objekt.