2016-09-29 2 views
0

D'abord, je suis pas demander la différence entre eux.Java Type d'arrivée equals(): instanceof vs getClass()

J'ai une idée à l'esprit et je voudrais plus le cerveau de vérifier, ce qui semble en mesure de prendre (la plupart) les avantages de l'approche utilisant à la fois.

En bref, equals() en Java ressemble généralement à ce sujet.

public boolean equals(Object other) { 
    if (other == null) return false; 
    if (this == other) return true; 
    if (this and other is not of the same type) return false 
    // then actual equals checking 
} 

Le problème réside dans le « cela et d'autres n'est pas du même type » vérifier.

Un flot de gens préfèrent utiliser if (! (other instanceof MyClass)), qui ont l'inconvénient de

  • facilement conduire à la suite asymétrique. Si autre est une sous-classe de MyClass et a substituée equals(), a.equals(b) et b.equals(a) ne sera pas la même
  • Pour éviter cela, nous devons éviter la classe des enfants de surcharger equals(), en le déclarant finale

L'autre flux de gens préfèrent utiliser if (getClass() != other.getClass()), qui a le défaut de

  • Violant principe de substitution Liskov. Une classe enfant de MyClass devrait être en mesure d'utiliser à la place de MyClass.
  • Par conséquent, a.equals(b) sera faux si a est de MyClass tandis que b est d'une classe enfant de MyClass, même les attributs internes sont identiques.

J'ai une idée à l'esprit, je veux que les gens vérifier.

Cette approche devrait

  1. Si une classe enfant substituée equals(), les résultats doivent encore être symétrique
  2. Une classe enfant pourra toujours être utilisé comme type de parent (dans l'aspect de equals() et equals n'est pas redéfinie bien sûr)

    public class Parent { 
        @Override 
        public boolean equals(Object other) { 
         if (other == null) return false; 
         if (this == other) return true; 
         if (!(other instanceof Parent)) return false; 
         try { 
          if (other.getClass().getMethod("equals", Object.class).getDeclaringClass() != Parent.class) { 
           return other.equals(this); 
          } 
         } catch(Exception neverHappen){} 
    
         // do actual checking 
        } 
    } 
    

L'idée principale est, si this rencontré un objet Parent, mais cet objet de equals() non déclarée dans Parent (à savoir cet objet est une classe enfant, avec equals() substituée, je vais déléguer l'invocation equals à l'objet de classe enfant (ou peut simplement renvoyer false).

Y at-il défaut d'utiliser cette approche que j'ai oublié? (Je suppose que la perte de performance sera une préoccupation, mais je crois que l'appel de getClass().method("equals").getDeclaringClass() devrait être assez bon marché?

+0

Non sûr, mais si la classe enfant fait 'super.equals (..)', vous obtiendrez un stackoverflow. Ainsi, vous finirez par réécrire la classe entière 'equals (..)' dans la classe enfant, ce qui est encore pire. – Asoub

+0

@ Asoub ah .... exactement ... Je n'ai joué que dans les cas de parent vs enfant, parent vs enfant surchargé, mais j'ai oublié d'essayer enfant surpassé vs enfant surchargé. Il s'est avéré que si je veux que les équivalents soient correctement remplacés, j'aurai besoin de méthodes séparées pour le faire correctement :( –

+0

@Asoub m'a rappelé quelque chose d'autre: J'ai toujours l'impression qu'utiliser getClass() dans type check de equals() donnera moi les résultats symétriques pour "extended equals" dans la classe enfant, et le seul inconvénient est la violation du principe de la substitution de Liskov.pendant une seconde cependant, il souffre aussi du problème que vous avez mentionné, que super.equals() ne peut pas être utilisé, isn –

Répondre

0

Y a-t-il un inconvénient à utiliser cette approche?

Cela ne fonctionnerait pas. La méthode à appeler est déterminée au moment de la compilation. Le polymorphisme s'applique uniquement à la référence avant . et non après.

other.equals(this); // always calls equals(Object); as `other` is an Object. 

également

other.getClass().getMethod("equals", /* method with no args */).getDeclaringClass(); 

Je suppose que vous vouliez. GetMethod other.getClass() ("égale", Parent.class), mais vous devez attraper NoSuchMethodException

Je crois call de getClass(). method ("equals"). getDeclaringClass() devrait être assez bon marché?

Pas dans ma sélection. C'est plus rapide que de lire quelque chose à partir du disque, mais vous ne voudriez pas le faire très souvent. Enfin, vous devez vous assurer que a.equals (b) ==> b.equals (a) et si a.equals (b) alors a.hashCode() == b.hashCode() qui est difficile à faire est a et b sont différents types.

+0

En fait, je veux dire 'getMethod (" est égal à ", Object.class)", qui est censé être la méthode surchargée.Je ne comprends pas le premier point, 'equals()' déclaré dans 'Object' est en fait' equals (Object) 'alors quel est le problème l'appeler? –

+0

pour hashCode, une fois y ou override est égal à, vous devez remplacer hashCode en même temps, ce qui ne devrait pas poser de problème. –

+0

@AdrianShum Vous pouvez appeler mais cela rend le chèque redondant. –

1

Comme indiqué dans les commentaires, si la classe enfant fait super.equals(..), vous obtiendrez un stackoverflow. Pour éviter cela, vous finirez par réécrire tous les parents equals(..) dans chaque enfant, ce qui est encore pire design.

D'une certaine manière, cela dépend du comment/pourquoi vous avez implémenté l'héritage en premier lieu. Un enfant devrait-il être comparable à un parent? Est-ce que la comparaison a du sens? (édité)

Si vous remplacez égal, alors vous ne respectez pas LSP, par définition. Si un enfant a plus de paramètres (ce qui signifie qu'il surpasse la méthode equals), alors il ne doit pas être égal à son parent, et c'est pourquoi nous pouvons comparer en utilisant getClass() != other.getClass() dans le parent. Si vous souhaitez utiliser la classe equals() de votre classe parent (vous n'avez donc pas à tout réécrire), vous ne finirez pas par stackoverflowing; equals() serait juste faux, car ils n'étaient pas censés être égaux.

Et si un enfant est comparable à son parent? Si vous respectez LSP, l'enfant ne devrait pas avoir un equals() différent de celui de son parent (ie: égal ne devrait pas être remplacé), je suppose. Donc le cas asymétrique ne devrait pas exister.

Si un enfant est comparable à son parent, mais a plus de paramètres? C'est maintenant votre conception, ne respectant pas LSP, donc c'est à vous de voir ce que cela signifie dans votre contexte et d'agir en conséquence.


EDIT: @Adrian oui, "ne symmmetry logique" était mauvaise formulation, je aurais dû dire "ce que le sens comparaison?".

Pour exemple, si vous comparez deux classes d'enfants avec getClass() et ils utilisent super avec aussi getClass() il retourne vrai (le test sera redondant, car this.getClass() et other.getClass() aura toujours les mêmes valeurs, chez l'enfant et le parent). Mais comme je l'ai dit, si vous comparez un enfant et un parent, ce sera faux (et je pense que c'est normal s'ils ont des paramètres différents).

Pourquoi utiliser final uniquement sur égal à avec instance de? Vous l'avez dit, à cause de l'asymétrie possible, alors qu'il n'est pas possible d'être assymétrique avec l'héritage en utilisant getClass(), donc inutile de le rendre final dans ce cas.

En note, si vous utilisez getClass(), alors plusieurs enfants du même parent ne seront pas comparables (toujours retourner faux). Si vous utilisez instanceof, ils le peuvent, mais s'ils le font, aucun enfant ne doit surcharger equals() pour risquer de rompre la symétrie. (Je pense que vous avez obtenu cela, mais je me demandais quand choisir insteanceof au lieu de getClass() si c'est si problématique).

+0

d'après ce que je comprends, la symétrie est toujours un comportement important pour 'equals()' (et aussi réflexif, transitif et cohérent). Donc, "la symétrie a un sens" ne devrait jamais être une question. Pour la question de "using super.equals()", je suppose que vous avez mal compris: ce que je veux dire, si nous sommes en train de surcharger 'equals()' et en utilisant 'getClass()' pour la vérification de type, bien que la classe enfant puisse seulement vérifier l'égalité contre la classe des enfants, il faut encore comparer sa partie "super". Donc, le code devrait ressembler à: –

+0

'boolean equals (Object o) {.....; if (this.getClass()! = that.getClass()) renvoie false; return super (que) && Objects.equals (this.childField, that.childField); } 'pour lequel il est également tombé dans le problème de l'incapacité de faire usage de« super.équals »(car il retournera toujours faux dans ce cas). Quoi qu'il en soit, un bon point dans l'interprétation de LSP. Cela m'apporte une autre confusion: Si c'est le cas, pourquoi les gens suggèrent de marquer 'equals()' final uniquement si vous utilisez 'instanceof' mais pas' getClass() '? Il devrait s'appliquer à l'un ou l'autre des cas n'est-ce pas? –

+0

@Adrian a ajouté un edit (trop long pour commenter). C'est une question assez intéressante, je n'y ai jamais autant pensé! Merci d'avoir demandé – Asoub