Les meilleures pratiques en matière d’égalité: surcharger ou ne pas surcharger?

Considérez l’extrait suivant:

import java.util.*; public class EqualsOverload { public static void main(Ssortingng[] args) { class Thing { final int x; Thing(int x) { this.x = x; } public int hashCode() { return x; } public boolean equals(Thing other) { return this.x == other.x; } } List myThings = Arrays.asList(new Thing(42)); System.out.println(myThings.contains(new Thing(42))); // prints "false" } } 

Notez que contains retours false !!! Nous semble avoir perdu nos affaires !!

Le bogue, bien sûr, est le fait que nous avons accidentellement surchargé , au lieu de surcharger , Object.equals(Object) . Si nous avions écrit la class Thing comme suit, alors renvoie true comme prévu.

  class Thing { final int x; Thing(int x) { this.x = x; } public int hashCode() { return x; } @Override public boolean equals(Object o) { return (o instanceof Thing) && (this.x == ((Thing) o).x); } } 

Java effectif 2e édition, article 36: Utilisez systématiquement l’annotation Override , utilise essentiellement le même argument pour recommander que @Override soit utilisé de manière cohérente. Ce conseil est bon, bien sûr, car si nous avions essayé de déclarer que @Override equals(Thing other) dans le premier extrait, notre petit compilateur sympathique nous indiquerait immédiatement notre petite erreur stupide, car il s’agit d’une surcharge et non d’un remplacement.

Ce que le livre ne couvre pas spécifiquement, cependant, est de savoir si la surcharge equals est une bonne idée pour commencer. Il existe essentiellement 3 situations:

  • Surcharge seulement, pas de neutralisation – PRESQUE CERTAINEMENT FAUX !
    • Ceci est essentiellement le premier extrait ci-dessus
  • Remplacer seulement (pas de surcharge) – une façon de corriger
    • Ceci est essentiellement le deuxième extrait ci-dessus
  • Combo surcharge et remplacement – une autre façon de réparer

La 3ème situation est illustrée par l’extrait suivant:

  class Thing { final int x; Thing(int x) { this.x = x; } public int hashCode() { return x; } public boolean equals(Thing other) { return this.x == other.x; } @Override public boolean equals(Object o) { return (o instanceof Thing) && (this.equals((Thing) o)); } } 

Ici, bien que nous ayons maintenant la méthode 2 equals , il existe toujours une logique d’égalité, située dans la surcharge. Le @Override délègue simplement à la surcharge.

Les questions sont donc les suivantes:

  • Quels sont les avantages et les inconvénients de “outrepasser que” vs “combo surcharge et outrepasser”?
  • Existe-t-il une justification pour une surcharge equals ou est-ce presque certainement une mauvaise pratique?

    Je ne verrais pas le cas de la surcharge égale, sauf que c’est plus sujet aux erreurs et plus difficile à maintenir, en particulier lorsque vous utilisez l’inheritance.

    Dans ce cas, il peut s’avérer extrêmement difficile de maintenir la réflexivité, la symésortinge et la transitivité ou de détecter leurs incohérences, car vous devez toujours connaître la méthode équivalente utilisée qui est invoquée. Il suffit de penser à une hiérarchie d’inheritance importante et à quelques-uns seulement des types implémentant leur propre méthode de surcharge.

    Donc, je dirais simplement ne le fais pas.

    Si vous avez un seul champ comme dans votre exemple, je pense

     @Override public boolean equals(Object o) { return (o instanceof Thing) && (this.x == ((Thing) o).x); } 

    est le chemin à parcourir. Tout le rest serait trop compliqué. Mais si vous ajoutez un champ (et que vous ne voulez pas dépasser la recommandation de 80 colonnes par soleil), cela ressemblerait à quelque chose comme:

     @Override public boolean equals(Object o) { if (!(o instanceof Thing)) return false; Thing t = (Thing) o; return this.x == tx && this.y == ty; } 

    ce que je pense est légèrement plus laid que

     public boolean equals(Thing o) { return this.x == ox && this.y == oy; } @Override public boolean equals(Object o) { // note that you don't need this.equals(). return (o instanceof Thing) && equals((Thing) o); } 

    Donc, ma règle de base est fondamentalement la suivante: si vous devez le convertir plus d’une fois dans override-only , effectuez le combo override- / surcharge .


    Un aspect secondaire est la surcharge d’exécution. Comme programmation de performance Java, Partie 2: Le coût du casting explique:

    Les opérations de downcast (également appelées ressortingctions de conversion dans la spécification du langage Java) convertissent une référence de classe ancêtre en une référence de sous-classe. Cette opération de transtockage crée une surcharge d’exécution, car Java requirejs que le transtypage soit vérifié au moment de l’exécution pour s’assurer de sa validité.

    En utilisant le combo surcharge / surpassement , le compilateur parviendra, dans certains cas (pas tous!) À se passer du downcast.


    Pour commenter le point @Snehal, le fait d’exposer les deux méthodes peut induire en erreur les développeurs côté client: une autre option serait de laisser les surcharges égales être privées. L’élégance est préservée, la méthode peut être utilisée en interne, tandis que l’interface avec le client se présente comme prévu.

    Problèmes avec égaux surchargés:

    • Toutes les Collections fournies par Java ie; Set, List, Map utilise la méthode redéfinie pour comparer deux objects. Ainsi, même si vous surchargez la méthode equals, cela ne résout pas le problème de comparer deux objects. En outre, si vous surchargez et implémentez simplement la méthode hashcode, vous obtiendrez un comportement erroné.

    • Si vous avez à la fois des méthodes égales surchargées et remplacées et que vous les exposez, vous allez confondre les développeurs côté client. C’est par convention que les gens croient que vous écrasez la classe Object

    Il y a un certain nombre d’éléments dans le livre qui couvrent cela. (Ce n’est pas devant moi, je vais donc faire référence aux éléments que je me souviens d’eux)

    Par exemple, on utilise exactement equals(..) où il est dit que la surcharge ne doit pas être utilisée et, le cas échéant, elle doit être utilisée avec précaution. L’élément relatif à la conception de la méthode met en garde contre la surcharge des méthodes avec le même nombre d’arguments. Alors, non, ne surchargez pas les equals(..)

    Mise à jour: à partir de “Effective Java” (p.44)

    Il est acceptable de fournir une méthode égale “fortement typée” en plus de la méthode normale, à condition que les deux méthodes donnent le même résultat, mais il n’y a aucune raison impérieuse de le faire.

    Il n’est donc pas interdit de le faire, mais cela ajoute de la complexité à votre classe, mais n’ajoute aucun gain.

    J’utilise cette approche avec une combinaison de remplacement et de surcharge dans mes projets, car le code est un peu plus propre. Je n’ai pas eu de problèmes avec cette approche jusqu’à présent.

    Je peux penser à un exemple très simple où cela ne fonctionnera pas correctement et pourquoi vous ne devriez jamais le faire:

     class A { private int x; public A(int x) { this.x = x; } public boolean equals(A other) { return this.x == other.x; } @Override public boolean equals(Object other) { return (other instanceof A) && equals((A) other); } } class B extends A{ private int y; public B(int x, int y) { super(x); this.y = y; } public boolean equals(B other) { return this.equals((A)other) && this.y == other.y; } @Override public boolean equals(Object other) { return (other instanceof B) && equals((B) other); } } public class Test { public static void main(Ssortingng[] args) { A a = new B(1,1); B b1 = new B(1,1); B b2 = new B(1,2); // This obviously returns false System.out.println(b1.equals(b2)); // What should this return? true! System.out.println(a.equals(b2)); // And this? Also true! System.out.println(b2.equals(a)); } } 

    Dans ce test, vous pouvez clairement voir que la méthode surchargée fait plus de mal que de bien en utilisant l’inheritance. Dans les deux cas incorrects, l’équivalent plus générique equals(A a) est appelé, car le compilateur Java sait uniquement que a est de type A et que cet object n’a pas la méthode surchargée equals(B b) .

    Après coup : rendre le surchargé equals privé résout ce problème, mais est-ce que cela vous fait vraiment gagner? Il ajoute uniquement une méthode supplémentaire, qui ne peut être appelée qu’en faisant une dissortingbution.