web-dev-qa-db-fra.com

List.contains () échoue pendant que .equals () fonctionne

J'ai un ArrayList d'objets Test, qui utilisent une chaîne comme vérification d'équivalence. Je veux pouvoir utiliser List.contains() pour vérifier si la liste contient ou non un objet qui utilise une certaine chaîne.

Simplement:

Test a = new Test("a");
a.equals("a"); // True

List<Test> test = new ArrayList<Test>();
test.add(a);
test.contains("a"); // False!

Fonction d'égalité et de hachage:

@Override
public boolean equals(Object o) {
    if (o == null) return false;
    if (o == this) return true;
    if (!(o instanceof Test)) {
        return (o instanceof String) && (name.equals(o));
    }
    Test t = (Test)o;
    return name.equals(t.GetName());
}

@Override
public int hashCode() {
    return name.hashCode();
}

J'ai lu que pour s'assurer que contains fonctionne pour une classe personnalisée, il doit remplacer equals. Il est donc très étrange pour moi que tandis que equals renvoie vrai, contains renvoie faux.

Comment puis-je faire fonctionner cela?

Code complet

32
idlackage

Le fait que votre Testequals puisse renvoyer true lorsque vous lui passez une chaîne ne signifie pas que String's equals reviendra toujours vrai lorsque vous passerez un Test instance. En fait, String's equals ne peut renvoyer true que lorsque l'instance qui lui est passée est un autre String:

public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    if (anObject instanceof String) { // the passed instance must be a String
        String anotherString = (String)anObject;
        int n = value.length;
        if (n == anotherString.value.length) {
            char v1[] = value;
            char v2[] = anotherString.value;
            int i = 0;
            while (n-- != 0) {
                if (v1[i] != v2[i])
                    return false;
                i++;
            }
            return true;
        }
    }
    return false;
}

ArrayList's contains appelle indexOf qui utilise la méthode equals de l'instance recherchée (le String "a" dans votre exemple), pas le type d'élément de List (qui est Test dans votre cas):

public int indexOf(Object o) {
    if (o == null) {
        for (int i = 0; i < size; i++)
            if (elementData[i]==null)
                return i;
    } else {
        for (int i = 0; i < size; i++)
            if (o.equals(elementData[i])) // o in your case is a String while
                                          // elementData[i] is a Test
                                          // so String's equals returns false
                return i;
    }
    return -1;
}
41
Eran

equals() doit toujours être commutative , c'est-à-dire a.equals(b) et b.equals(a) doit toujours renvoyer la même valeur . Ou symétrique, comme le javadoc de equals() l'appelle:

La méthode equals implémente une relation d'équivalence sur les références d'objet non nul:

  • C'est reflexive: pour toute valeur de référence non nulle x, x.equals(x) devrait retourner true.
  • C'est symétrique: pour toutes les valeurs de référence non nulles x et y, x.equals(y) devrait retourner true si et seulement si y.equals(x) renvoie true.
  • C'est transitive: pour toutes les valeurs de référence non nulles x, y et z, si x.equals(y) renvoie true et y.equals(z) renvoie true, puis x.equals(z) devrait renvoyer true.
  • C'est cohérent: pour toutes les valeurs de référence non nulles x et y, plusieurs invocations de x.equals(y) retournent systématiquement true ou renvoie systématiquement false, à condition qu'aucune information utilisée dans les comparaisons equals sur les objets ne soit modifiée.
  • Pour toute valeur de référence non nulle x, x.equals(null) doit renvoyer false.

Malheureusement, même la Java Runtime Library se trompe. Date.equals(Timestamp) comparera les valeurs en millisecondes, ignorant les nanosecondes présentes dans le Timestamp, tandis que Timestamp.equals(Date) renvoie false.

27
Andreas

Le problème est que List<E>.contains(object o) est documenté pour retourner vrai:

si et seulement si cette liste contient au moins un élément e tel que (o == null? e == null: o.equals (e)).

(Depuis https://docs.Oracle.com/javase/8/docs/api/Java/util/List.html#contains-Java.lang.Object- )

Notez qu'il n'effectue pas le test en tant que e.equals(o), ce qui serait nécessaire pour que votre test fonctionne. Votre méthode d'égalité ne fonctionne pas de manière commutative ("symétriquement" en utilisant les termes des documents Java).

Documents Java indiquant que la méthode equals() pour une classe doit suivre ces règles:

La méthode equals implémente une relation d'équivalence sur les références d'objets non nulles:

  • Il est réflexif: pour toute valeur de référence non nulle, x, x.equals(x) doit renvoyer true.
  • Il est symétrique: pour toutes les valeurs de référence non nulles x et y, x.equals(y) devrait retourner vrai si et seulement si y.equals(x) renvoie true.
  • Elle est transitive: pour toutes les valeurs de référence non nulles x, y et z, si x.equals(y) renvoie true et y.equals(z) retourne vrai, puis x.equals(z) devrait retourner vrai.
  • Il est cohérent: pour toutes les valeurs de référence non nulles x et y, plusieurs invocations de x.equals(y) retournent systématiquement true ou retournent systématiquement false, à condition qu'aucune information ne soit utilisée dans les comparaisons égales sur les objets est modifié.
  • Pour toute valeur de référence non nulle, x, x.equals(null) doit renvoyer false.
18
Michael Burr

Si vous écrivez

test.contains(new Test("a")); 

alors cela reviendra sûrement vrai. Vous recherchez l'objet chaîne dans la liste de test.

5
Kunal Surana