1. Confronto di oggetti in Java

In Java, gli oggetti possono essere confrontati sia per riferimento che per valore.

Referenze a confronto

Se due variabili puntano allo stesso oggetto in memoria, i riferimenti memorizzati in queste variabili sono uguali. Se si confrontano queste variabili utilizzando l'operatore di uguaglianza ( ==), si ottiene true e il risultato ha senso. Tutto è semplice qui.

Codice Uscita console
Integer a = 5;
Integer b = a;
System.out.println(a == b);


true

Confronto per valore

Ma spesso puoi incontrare situazioni in cui due variabili si riferiscono a due oggetti distinti che sono identici. Ad esempio, due diversi oggetti stringhe che contengono lo stesso testo.

Per determinare se diversi oggetti sono identici, utilizzare il equals()metodo. Per esempio:

Codice Uscita console
String a = new String("Hello");
String b = new String("Hello");
System.out.println(a == b);
System.out.println(a.equals(b));


false
true

Il equalsmetodo non è limitato alla Stringclasse. Ogni classe ce l'ha.

Anche lezioni che scrivi da solo, ed ecco perché.



2. Objectclasse

Tutte le classi in Java ereditano la Objectclasse. I creatori di Java hanno escogitato questo approccio.

E se una classe eredita la Objectclasse, ottiene tutti i metodi della Objectclasse. E questa è una conseguenza importante dell'ereditarietà.

In altre parole, ogni classe ha i metodi della Objectclasse, anche se il loro codice non li menziona.

Questi metodi ereditati includono metodi relativi al confronto di oggetti. Questi sono i metodi equals()e hashCode().

Codice In realtà, ecco cosa avremo:
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
   }
}

Nell'esempio sopra, abbiamo creato una Personclasse semplice con parametri nome ed età, ma non un singolo metodo. Ma poiché tutte le classi ereditano la Objectclasse, la Personclasse ha automaticamente due metodi:

Metodo Descrizione
boolean equals(Object obj)
Confronta l'oggetto corrente e l'oggetto passato
int hashCode()
Restituisce il codice hash dell'oggetto corrente

Si scopre che assolutamente ogni oggetto ha il equalsmetodo e oggetti di tipi diversi possono essere confrontati tra loro. Tale codice verrà compilato e funzionerà perfettamente.

Codice Uscita console
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()metodo

Il equals()metodo, ereditato dalla Objectclasse, implementa l'algoritmo più semplice per confrontare l'oggetto corrente con gli oggetti passati: confronta solo i riferimenti agli oggetti.

Ottieni lo stesso risultato se confronti solo Personle variabili invece di chiamare il equals()metodo. Esempio:

Codice Uscita console
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

Quando il equalsmetodo viene chiamato a, confronta semplicemente il riferimento memorizzato nella avariabile con il riferimento memorizzato nella bvariabile.

Tuttavia, il confronto funziona in modo diverso per la Stringclasse. Perché?

Perché le persone che hanno creato la Stringclasse hanno scritto la propria implementazione del equals()metodo.

Implementazione del equals()metodo

Ora scriviamo la nostra implementazione del metodo equals nella Personclasse. Prenderemo in considerazione 4 casi principali.

Importante:
Indipendentemente da quale classe sovrascrive il equalsmetodo, prende sempre un Objectoggetto come argomento

Scenario 1 : lo stesso oggetto su cui equalsviene chiamato il metodo viene passato anche al equalsmetodo. Se i riferimenti dell'oggetto corrente e dell'oggetto passato sono uguali, il metodo deve restituire true. Un oggetto è uguale a se stesso.

Nel codice sarà simile a questo:

Codice Descrizione
public boolean equals(Object obj)
{
   if (this == obj)
    return true;

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


Confronta i riferimenti

Scenario 2 : nullviene passato al equalsmetodo: non abbiamo nulla con cui confrontarci. L'oggetto su cui viene chiamato il metodo non è sicuramente nullo, quindi in questo caso equalsdobbiamo tornare .false

Nel codice sarà simile a questo:

Codice Descrizione
public boolean equals(Object obj)
{
   if (this == obj)
      return true;

   if (obj == null)
      return false;

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


Confronta i riferimenti


È l'oggetto passato null?

Scenario 3 : un riferimento a un oggetto che non è a Personviene passato al equalsmetodo. L'oggetto è Personuguale al non Personoggetto? Questa è una domanda che lo sviluppatore della Personclasse deve decidere come vuole.

Ma di solito gli oggetti devono essere della stessa classe per essere considerati uguali. Pertanto, se al nostro metodo equals viene passato qualcosa di diverso da un oggetto della Personclasse, restituiremo sempre false. Come puoi controllare il tipo di un oggetto? Esatto, utilizzando l' instanceofoperatore.

Ecco come appare il nostro nuovo codice:

Codice Descrizione
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
}


Confronta i riferimenti


È l'oggetto passato null?


Se l'oggetto passato non è aPerson

4. Confrontare due Personoggetti

Con cosa siamo finiti? Se abbiamo raggiunto la fine del metodo, allora abbiamo un Personriferimento all'oggetto che non è null. Quindi lo convertiamo in a Persone confrontiamo i dati interni rilevanti di entrambi gli oggetti. E questo è il nostro quarto scenario .

Codice Descrizione
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
}


Confronta i riferimenti


È l'oggetto passato null?


Se l'oggetto passato non è un Person


Typecasting

E come si confrontano due Personoggetti? Sono uguali se hanno lo stesso nome ( name) ed età ( age). Il codice finale sarà simile a questo:

Codice Descrizione
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;
}


Confronta i riferimenti


È l'oggetto passato null?


Se l'oggetto passato non è un Person


Typecasting

Ma non è tutto.

Innanzitutto, il campo del nome è un String, quindi è necessario confrontare il campo del nome chiamando il equalsmetodo.

this.name.equals(person.name)

In secondo luogo, il namecampo potrebbe essere null: in tal caso, non puoi richiamarlo equals. Hai bisogno di un assegno aggiuntivo per null:

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

Detto questo, se il campo del nome si trova nullin entrambi Persongli oggetti, i nomi sono comunque uguali.

Il codice per il quarto scenario potrebbe essere simile al seguente:

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


Se le età non sono uguali,
immediatamente return false

If this.nameè uguale a null, non ha senso confrontare usando il equalsmetodo. Qui o il secondo namecampo è uguale a nullo non lo è.

Confronta i due campi del nome utilizzando il equalsmetodo.


5. hashCode()metodo

Oltre al equalsmetodo, che ha lo scopo di eseguire un confronto dettagliato di tutti i campi di entrambi gli oggetti, esiste un altro metodo che può essere utilizzato per un confronto impreciso ma molto rapido: hashCode().

Immagina di ordinare alfabeticamente un elenco di migliaia di parole e di dover confrontare ripetutamente coppie di parole. E le parole sono lunghe, composte da molte lettere. In generale, un simile confronto richiederebbe molto tempo.

Ma può essere accelerato. Supponiamo di avere parole che iniziano con lettere diverse: è subito chiaro che sono diverse. Ma se iniziano con le stesse lettere, allora non possiamo ancora dire quale sarà il risultato: le parole potrebbero risultare uguali o diverse.

Il hashCode()metodo funziona utilizzando un principio simile. Se lo chiami su un oggetto, restituisce un numero, analogo alla prima lettera di una parola. Questo numero ha le seguenti proprietà:

  • Oggetti identici hanno sempre lo stesso hashcode
  • Oggetti diversi possono avere lo stesso codice hash o i loro codici hash possono essere diversi
  • Se gli oggetti hanno codici hash diversi, allora gli oggetti sono decisamente diversi

Per renderlo ancora più chiaro, riformuliamo queste proprietà in termini di parole:

  • Le parole identiche hanno sempre le stesse prime lettere.
  • Parole diverse possono avere le stesse prime lettere o le loro prime lettere possono essere diverse
  • Se le parole hanno lettere iniziali diverse, allora le parole sono decisamente diverse

L'ultima proprietà viene utilizzata per accelerare il confronto degli oggetti:

Innanzitutto, vengono calcolati gli hashcode dei due oggetti. Se questi codici hash sono diversi, gli oggetti sono decisamente diversi e non è necessario confrontarli ulteriormente.

Ma se gli hashcode sono gli stessi, dobbiamo comunque confrontare gli oggetti usando il metodo equals.



6. Contratti in codice

Il comportamento sopra descritto deve essere implementato da tutte le classi in Java. Durante la compilazione, non è possibile verificare se gli oggetti vengono confrontati correttamente.

I programmatori Java hanno un accordo universale sul fatto che se scrivono la propria implementazione del metodo equals() e quindi sovrascrivono l'implementazione standard (nella Objectclasse), devono anche scrivere la propria implementazione del hashCode()metodo in modo tale che le suddette regole siano soddisfatto.

Questa disposizione è chiamata contratto .

Se implementi solo il equals()o solo il hashCode()metodo nella tua classe, allora sei in grave violazione del contratto (hai rotto l'accordo). Non farlo.

Se altri programmatori utilizzano il tuo codice, potrebbe non funzionare correttamente. Inoltre, utilizzerai un codice che si basa sull'adesione ai contratti di cui sopra.

Importante!

Durante la ricerca di un elemento, tutte le raccolte Java confrontano prima i codici hash degli oggetti e solo successivamente eseguono un confronto utilizzando il equalsmetodo.

Ciò significa che se dai alla tua classe un equalsmetodo ma non scrivi il tuo hashCode()metodo o lo implementi in modo errato, le raccolte potrebbero non funzionare correttamente con i tuoi oggetti.

Ad esempio, potresti aggiungere un oggetto a un elenco e quindi cercarlo utilizzando il contains()metodo, ma la raccolta potrebbe non trovare il tuo oggetto.