Java Language equals () metodo


Esempio

TL; DR

== verifica l'uguaglianza di riferimento (indipendentemente dal fatto che siano lo stesso oggetto )

.equals() verifica l'uguaglianza dei valori (indipendentemente dal fatto che siano logicamente "uguali" )


equals() è un metodo utilizzato per confrontare due oggetti per l'uguaglianza. L'implementazione predefinita del metodo equals() nella classe Object restituisce true se e solo se entrambi i riferimenti puntano alla stessa istanza. Si comporta quindi come il confronto di == .

public class Foo {
    int field1, field2;
    String field3;

    public Foo(int i, int j, String k) {
        field1 = i;
        field2 = j;
        field3 = k;
    }

    public static void main(String[] args) {
        Foo foo1 = new Foo(0, 0, "bar");
        Foo foo2 = new Foo(0, 0, "bar");

        System.out.println(foo1.equals(foo2)); // prints false
    }
}

Anche se foo1 e foo2 sono creati con gli stessi campi, stanno puntando a due oggetti diversi in memoria. L'implementazione di default equals() quindi è considerata false .

Per confrontare il contenuto di un oggetto per l'uguaglianza, equals() deve essere sovrascritto.

public class Foo {
    int field1, field2;
    String field3;

    public Foo(int i, int j, String k) {
        field1 = i;
        field2 = j;
        field3 = k;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null || getClass() != obj.getClass()) {
            return false;
        }

        Foo f = (Foo) obj;
        return field1 == f.field1 &&
               field2 == f.field2 &&
               (field3 == null ? f.field3 == null : field3.equals(f.field3));
    }

    @Override
    public int hashCode() {
        int hash = 1;
        hash = 31 * hash + this.field1;
        hash = 31 * hash + this.field2;
        hash = 31 * hash + (field3 == null ? 0 : field3.hashCode());
        return hash;
    }

    public static void main(String[] args) {
        Foo foo1 = new Foo(0, 0, "bar");
        Foo foo2 = new Foo(0, 0, "bar");

        System.out.println(foo1.equals(foo2)); // prints true
    }
}

Qui il metodo equals() over equals() sovrascritto decide che gli oggetti sono uguali se i loro campi sono uguali.

Si noti che anche il metodo hashCode() è stato sovrascritto. Il contratto per tale metodo afferma che quando due oggetti sono uguali, i loro valori hash devono essere uguali. Ecco perché bisogna quasi sempre sostituire hashCode() ed equals() insieme.

Presta particolare attenzione al tipo di argomento del metodo equals . È Object obj , non Foo obj . Se metti quest'ultimo nel tuo metodo, questo non è un override del metodo equals .

Quando scrivi la tua classe, dovrai scrivere una logica simile quando esegui l'override di equals() e hashCode() . La maggior parte degli IDE può generare automaticamente questo per te.

Un esempio di un'implementazione equals() può essere trovato nella classe String , che fa parte dell'API Java principale. Piuttosto che confrontare i puntatori, la classe String confronta il contenuto della String .

Java SE 7

Java 1.7 ha introdotto la classe java.util.Objects che fornisce un metodo di convenienza, equals , che confronta due riferimenti potenzialmente null , quindi può essere utilizzato per semplificare le implementazioni del metodo equals .

@Override
public boolean equals(Object obj) {
    if (this == obj) {
        return true;
    }
    if (obj == null || getClass() != obj.getClass()) {
        return false;
    }

    Foo f = (Foo) obj;
    return field1 == f.field1 && field2 == f.field2 && Objects.equals(field3, f.field3);
}

Confronto di classe

Poiché il metodo equals può essere eseguito su qualsiasi oggetto, una delle prime cose che il metodo spesso esegue (dopo aver verificato il null ) consiste nel verificare se la classe dell'oggetto confrontato corrisponde alla classe corrente.

@Override
public boolean equals(Object obj) {
    //...check for null
    if (getClass() != obj.getClass()) {
        return false;
    }
    //...compare fields
}

Questo è tipicamente fatto come sopra confrontando gli oggetti di classe. Tuttavia, ciò può fallire in alcuni casi speciali che potrebbero non essere ovvi. Ad esempio, alcuni framework generano proxy dinamici di classi e questi proxy dinamici sono in realtà una classe diversa. Ecco un esempio usando JPA.

Foo detachedInstance = ...
Foo mergedInstance = entityManager.merge(detachedInstance);
if (mergedInstance.equals(detachedInstance)) {
    //Can never get here if equality is tested with getClass()
    //as mergedInstance is a proxy (subclass) of Foo
}

Un meccanismo per ovviare a questa limitazione è confrontare le classi usando instanceof

@Override
public final boolean equals(Object obj) {
    if (!(obj instanceof Foo)) {
        return false;
    }
    //...compare fields
}

Tuttavia, ci sono alcune insidie ​​che devono essere evitate quando si usa instanceof . Dato che Foo potrebbe potenzialmente avere altre sottoclassi e quelle sottoclassi potrebbero sovrascrivere equals() potresti entrare in un caso in cui un Foo è uguale a FooSubclass ma FooSubclass non è uguale a Foo .

Foo foo = new Foo(7);
FooSubclass fooSubclass = new FooSubclass(7, false);
foo.equals(fooSubclass) //true
fooSubclass.equals(foo) //false

Ciò viola le proprietà di simmetria e transitività e quindi è un'implementazione non valida del metodo equals() . Di conseguenza, quando si usa instanceof , una buona pratica consiste nel rendere final metodo equals() (come nell'esempio precedente). Ciò garantirà che nessuna sottoclasse sovrascrive equals() e viola le ipotesi chiave.