Java Language méthode equals ()


Exemple

TL; DR

== teste l'égalité de référence (qu'ils soient le même objet )

.equals() teste l'égalité des valeurs (qu'elles soient logiquement "égales" )


equals() est une méthode utilisée pour comparer deux objets pour l'égalité. L'implémentation par défaut de la méthode equals() dans la classe Object renvoie true si et seulement si les deux références pointent vers la même instance. Il se comporte donc comme la comparaison par == .

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

Même si foo1 et foo2 sont créés avec les mêmes champs, ils pointent vers deux objets différents en mémoire. Par conséquent, l'implémentation equals() par défaut equals() évaluée à false .

Pour comparer le contenu d'un objet à l'égalité, equals() doit être remplacé.

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

Ici, la méthode equals() surchargée décide que les objets sont égaux si leurs champs sont identiques.

Notez que la hashCode() a également été remplacée. Le contrat de cette méthode indique que lorsque deux objets sont égaux, leurs valeurs de hachage doivent également être identiques. C'est pourquoi il faut presque toujours remplacer hashCode() et equals() ensemble.

Portez une attention particulière au type d’argument de la méthode equals . C'est Object obj , pas Foo obj . Si vous mettez cette dernière dans votre méthode, cela ne remplace pas la méthode equals .

Lors de l'écriture de votre propre classe, vous devrez écrire une logique similaire lors de la substitution de equals() et hashCode() . La plupart des IDE peuvent automatiquement générer cela pour vous.

Un exemple d'implémentation equals() peut être trouvé dans la classe String , qui fait partie de l'API Java principale. Plutôt que de comparer des pointeurs, la classe String compare le contenu de la String .

Java SE 7

Java 1.7 a introduit la classe java.util.Objects qui fournit une méthode pratique, equals , qui compare deux références potentiellement null , afin de simplifier les implémentations de la méthode 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);
}

Comparaison de classes

Comme la méthode equals peut s'exécuter sur n'importe quel objet, l'une des premières actions de la méthode (après avoir vérifié la valeur null ) consiste à vérifier si la classe de l'objet comparé correspond à la classe en cours.

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

Cela se fait généralement comme ci-dessus en comparant les objets de classe. Cependant, cela peut échouer dans quelques cas particuliers qui peuvent ne pas être évidents. Par exemple, certains frameworks génèrent des proxies dynamiques de classes et ces proxy dynamiques sont en fait une classe différente. Voici un exemple d'utilisation de 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 mécanisme pour contourner cette limitation consiste à comparer les classes en utilisant instanceof

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

Cependant, il y a quelques pièges à éviter lors de l'utilisation de instanceof . Puisque Foo pourrait potentiellement avoir d'autres sous-classes et que ces sous-classes pourraient remplacer equals() vous pourriez entrer dans un cas où un Foo est égal à un FooSubclass mais le FooSubclass n'est pas égal à Foo .

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

Cela viole les propriétés de symétrie et de transitivité et constitue donc une implémentation invalide de la méthode equals() . Par conséquent, lorsque vous utilisez instanceof , une bonne pratique consiste à rendre la méthode equals() final (comme dans l'exemple ci-dessus). Cela garantira qu'aucune sous-classe ne remplace equals() et viole les hypothèses clés.