Une raison de préférer getClass () à instanceof lors de la génération de .equals ()?

174

J'utilise Eclipse pour générer .equals()et .hashCode(), et il existe une option intitulée "Utiliser 'instanceof' pour comparer les types". Par défaut, cette option est décochée et utilisée .getClass()pour comparer les types. Y at - il raison que je préférerais .getClass()plus instanceof?

Sans utiliser instanceof:

if (obj == null)
  return false;
if (getClass() != obj.getClass())
  return false;

Utilisation instanceof:

if (obj == null)
  return false;
if (!(obj instanceof MyClass))
  return false;

Je coche habituellement l' instanceofoption, puis j'entre et je supprime le " if (obj == null)" chèque. (Il est redondant car les objets nuls échoueront toujours instanceof.) Y a-t-il une raison pour laquelle c'est une mauvaise idée?

Kip
la source
1
L'expression x instanceof SomeClassest fausse si xc'est null. Par conséquent, la deuxième syntaxe n'a pas besoin de la vérification nulle.
rds
5
@rds Oui, le paragraphe immédiatement après l'extrait de code le dit également. C'est dans l'extrait de code, car c'est ce que génère Eclipse.
Kip le

Réponses:

100

Si vous utilisez instanceof, ce qui rend votre equalsmise en œuvre finalpréservera le contrat de symétrie de la méthode: x.equals(y) == y.equals(x). Si cela finalsemble restrictif, examinez attentivement votre notion d'équivalence d'objet pour vous assurer que vos implémentations prédominantes maintiennent pleinement le contrat établi par la Objectclasse.

Erickson
la source
c'est exactement ce qui m'est venu à l'esprit lorsque j'ai lu la citation de josh bloch ci-dessus. +1 :)
Johannes Schaub - litb
13
certainement la décision clé. la symétrie doit s'appliquer, et instanceof rend très facile d'être asymétrique accidentellement
Scott Stanchfield
J'ajouterais aussi que "si finalsemble restrictif" (sur les deux equalset hashCode) alors utiliser l' getClass()égalité au lieu de instanceofafin de préserver les exigences de symétrie et de transitivité du equalscontrat.
Shadow Man
176

Josh Bloch privilégie votre approche:

La raison pour laquelle je préfère l' instanceofapproche est que lorsque vous utilisez l' getClassapproche, vous avez la restriction selon laquelle les objets ne sont égaux qu'à d'autres objets de la même classe, du même type d'exécution. Si vous étendez une classe et y ajoutez quelques méthodes inoffensives, vérifiez si un objet de la sous-classe est égal à un objet de la super classe, même si les objets sont égaux dans tous les aspects importants, vous obtiendrez le réponse surprenante qu'ils ne sont pas égaux. En fait, cela viole une interprétation stricte du principe de substitution de Liskov et peut conduire à un comportement très surprenant. En Java, c'est particulièrement important car la plupart des collections (HashTable, etc.) sont basées sur la méthode des égaux. Si vous placez un membre de la super classe dans une table de hachage en tant que clé et que vous le recherchez à l'aide d'une instance de sous-classe, vous ne le trouverez pas, car ils ne sont pas égaux.

Voir aussi cette réponse SO .

Le chapitre 3 de Effective Java couvre également cela.

Michael Myers
la source
18
+1 Tous ceux qui font du java devraient lire ce livre au moins 10 fois!
André
16
Le problème avec l'approche instanceof est qu'elle rompt la propriété "symétrique" d'Object.equals () en ce qu'il devient possible que x.equals (y) == true mais y.equals (x) == false si x.getClass ()! = y.getClass (). Je préfère ne pas casser cette propriété à moins que cela ne soit absolument nécessaire (c'est-à-dire remplacer equals () pour les objets gérés ou proxy).
Kevin Sitze
8
L'approche instanceof est appropriée lorsque, et seulement quand, la classe de base définit ce que l'égalité entre les objets de sous-classe devrait signifier. L'utilisation getClassne viole pas le LSP, puisque le LSP se rapporte simplement à ce qui peut être fait avec des instances existantes - pas aux types d'instances qui peuvent être construites. La classe renvoyée par getClassest une propriété immuable d'une instance d'objet. Le LSP n'implique pas qu'il devrait être possible de créer une sous-classe où cette propriété indique une classe autre que celle qui l'a créée.
supercat
66

Angelika Langers Secrets of equal entre dans cela avec une discussion longue et détaillée pour quelques exemples courants et bien connus, y compris par Josh Bloch et Barbara Liskov, découvrant quelques problèmes dans la plupart d'entre eux. Elle obtient également dans le instanceofcontre getClass. Quelques citations

Conclusions

Après avoir disséqué les quatre exemples choisis arbitrairement d'implémentations de equals (), que concluons-nous?

Tout d'abord: il existe deux manières substantiellement différentes d'effectuer la vérification de la correspondance de type dans une implémentation de equals (). Une classe peut permettre une comparaison de type mixte entre des objets super et sous-classe au moyen de l'opérateur instanceof, ou une classe peut traiter des objets de type différent comme non égaux au moyen du test getClass (). Les exemples ci-dessus illustrent bien que les implémentations de equals () utilisant getClass () sont généralement plus robustes que celles utilisant instanceof.

Le test instanceof n'est correct que pour les classes finales ou si au moins la méthode equals () est finale dans une superclasse. Ce dernier implique essentiellement qu'aucune sous-classe ne doit étendre l'état de la superclasse, mais ne peut ajouter que des fonctionnalités ou des champs qui ne sont pas pertinents pour l'état et le comportement de l'objet, tels que les champs transitoires ou statiques.

Les implémentations utilisant le test getClass () par contre respectent toujours le contrat equals (); ils sont corrects et robustes. Cependant, ils sont sémantiquement très différents des implémentations qui utilisent l'instance de test. Les implémentations utilisant getClass () ne permettent pas de comparer des objets de sous-classe avec des objets de superclasse, même si la sous-classe n'ajoute aucun champ et ne souhaite même pas remplacer equals (). Une telle extension de classe "triviale" serait par exemple l'ajout d'une méthode debug-print dans une sous-classe définie exactement pour cet objectif "trivial". Si la superclasse interdit la comparaison de type mixte via la vérification getClass (), alors l'extension triviale ne sera pas comparable à sa superclasse. Que ce soit un problème ou non dépend entièrement de la sémantique de la classe et du but de l'extension.

Johannes Schaub - litb
la source
61

La raison de l'utilisation getClassest d'assurer la propriété symétrique du equalscontrat. De Equals 'JavaDocs:

Il est symétrique: pour toutes les valeurs de référence non nulles x et y, x.equals (y) doit retourner vrai si et seulement si y.equals (x) renvoie vrai.

En utilisant instanceof, il est possible de ne pas être symétrique. Prenons l'exemple: Dog étend Animal. Animal's equalseffectue une instanceofvérification d'Animal. Dog's equalsfait une instanceofvérification de Dog. Donnez Animal a et Chien d (avec les autres champs identiques):

a.equals(d) --> true
d.equals(a) --> false

Cela viole la propriété symétrique.

Pour suivre strictement le contrat d'égal, la symétrie doit être assurée, et donc la classe doit être la même.

Steve Kuo
la source
8
C'est la première réponse claire et concise à cette question. Un échantillon de code vaut mille mots.
Johnride
J'étais en train de passer en revue toutes les autres réponses verbeuses que vous avez sauvées de la journée.Simple, concise, élégante et tout à fait la meilleure réponse à cette question.
1
Il y a l'exigence de symétrie comme vous l'avez mentionné. Mais il y a aussi l'exigence de «transitivité». Si a.equals(c)et b.equals(c), alors a.equals(b)(l'approche naïve de faire Dog.equalsjuste return super.equals(object)quand !(object instanceof Dog)mais de vérifier les champs supplémentaires quand il s'agit d'une instance de Dog ne violerait pas la symétrie mais violerait la transitivité)
Shadow Man
Pour être sûr, vous pouvez utiliser une getClass()vérification d'égalité, ou vous pouvez utiliser une instanceofvérification si make your equalset hashCodeméthodes final.
Shadow Man
26

C'est en quelque sorte un débat religieux. Les deux approches ont leurs problèmes.

  • Utilisez instanceof et vous ne pourrez jamais ajouter de membres significatifs aux sous-classes.
  • Utilisez getClass et vous enfreignez le principe de substitution de Liskov.

Bloch a un autre conseil pertinent dans Effective Java Second Edition :

  • Point 17: Conception et document pour l'héritage ou l'interdire
McDowell
la source
1
Rien à propos de l'utilisation getClassne violerait le LSP, à moins que la classe de base ne documente spécifiquement un moyen par lequel il devrait être possible de rendre les instances de différentes sous-classes qui se comparent égales. Quelle violation de LSP voyez-vous?
supercat
Peut-être que cela devrait être reformulé comme Use getClass et vous laissez les sous-types en danger de violations de LSP. Bloch a un exemple en Java efficace - également discuté ici . Son raisonnement est en grande partie que cela peut entraîner un comportement surprenant pour les développeurs implémentant des sous-types qui n'ajoutent pas d'état. Je comprends votre point - la documentation est essentielle.
McDowell
2
La seule fois où deux instances de classes distinctes devraient se rapporter est égal si elles héritent d'une classe de base commune ou d'une interface qui définit ce que signifie l'égalité [une philosophie que Java a appliquée, à mon humble avis, à certaines interfaces de collection]. À moins que le contrat de classe de base ne dise que cela getClass()ne devrait pas être considéré comme significatif au-delà du fait que la classe en question est convertible en classe de base, le retour de getClass()devrait être considéré comme toute autre propriété qui doit correspondre pour que les instances soient égales.
supercat
1
@eyalzba Où instanceof_ est utilisé si une sous-classe ajoutait des membres au contrat égal, cela violerait l' exigence d'égalité symétrique . L' utilisation des sous-classes getClass ne peut jamais être égale au type parent. Non pas que vous deviez de toute façon remplacer les égaux, mais c'est le compromis si vous le faites.
McDowell
2
"Concevoir et documenter l'héritage ou l'interdire" Je pense que c'est très important. Je crois comprendre que le seul moment où vous devez remplacer égal à égal est si vous créez un type valeur immuable, auquel cas la classe doit être déclarée finale. Puisqu'il n'y aura pas d'héritage, vous êtes libre d'implémenter equals si nécessaire. Dans les cas où vous autorisez l'héritage, les objets doivent se comparer par égalité de référence.
TheSecretSquad
23

Corrigez-moi si je me trompe, mais getClass () sera utile lorsque vous voulez vous assurer que votre instance n'est PAS une sous-classe de la classe avec laquelle vous comparez. Si vous utilisez instanceof dans cette situation, vous ne pouvez PAS le savoir car:

class A { }

class B extends A { }

Object oA = new A();
Object oB = new B();

oA instanceof A => true
oA instanceof B => false
oB instanceof A => true // <================ HERE
oB instanceof B => true

oA.getClass().equals(A.class) => true
oA.getClass().equals(B.class) => false
oB.getClass().equals(A.class) => false // <===============HERE
oB.getClass().equals(B.class) => true
MarchandJoeChicago
la source
Pour moi, c'est la raison
karlihnos
5

Si vous voulez vous assurer que seule cette classe correspondra, utilisez getClass() ==. Si vous souhaitez faire correspondre des sous-classes, il instanceofest nécessaire.

En outre, instanceof ne correspondra pas à un null mais peut être comparé en toute sécurité à un null. Vous n'avez donc pas besoin de le vérifier.

if ( ! (obj instanceof MyClass) ) { return false; }
Clint
la source
Concernant votre deuxième point: il l'a déjà mentionné dans la question. "Je vérifie généralement l'option instanceof, puis j'y vais et je supprime la vérification 'if (obj == null)'."
Michael Myers
5

Cela dépend si vous considérez si une sous-classe d'une classe donnée est égale à son parent.

class LastName
{
(...)
}


class FamilyName
extends LastName
{
(..)
}

ici, j'utiliserais 'instanceof', car je veux qu'un LastName soit comparé à FamilyName

class Organism
{
}

class Gorilla extends Organism
{
}

ici, j'utiliserais «getClass», car la classe dit déjà que les deux instances ne sont pas équivalentes.

Pierre
la source
3

instanceof fonctionne pour les instances de la même classe ou ses sous-classes

Vous pouvez l'utiliser pour tester si un objet est une instance d'une classe, une instance d'une sous-classe ou une instance d'une classe qui implémente une interface particulière.

ArryaList et RoleList sont tous deux instances de List

Tandis que

getClass () == o.getClass () ne sera vrai que si les deux objets (this et o) appartiennent exactement à la même classe.

Donc, en fonction de ce que vous devez comparer, vous pouvez utiliser l'un ou l'autre.

Si votre logique est: "Un objet est égal à un autre seulement s'ils sont tous les deux de la même classe", vous devriez opter pour les "égaux", ce qui, je pense, est la plupart des cas.

OscarRyz
la source
3

Les deux méthodes ont leurs problèmes.

Si la sous-classe change l'identité, vous devez comparer leurs classes réelles. Sinon, vous violez la propriété symétrique. Par exemple, différents types dePerson s ne doivent pas être considérés comme équivalents, même s'ils portent le même nom.

Cependant, certaines sous-classes ne changent pas d'identité et doivent être utilisées instanceof. Par exemple, si nous avons un tas d' Shapeobjets immuables , alors un Rectangleavec une longueur et une largeur de 1 devrait être égal à l'unitéSquare .

En pratique, je pense que le premier cas est plus vraisemblablement vrai. Habituellement, le sous-classement est une partie fondamentale de votre identité et être exactement comme votre parent sauf que vous pouvez faire une petite chose ne vous rend pas égal.

James
la source
-1

En fait, instanceof vérifie où un objet appartient à une hiérarchie ou non. ex: l'objet Car appartient à la classe Vehical. Donc "nouvelle instance Car () de Vehical" renvoie true. Et "new Car (). GetClass (). Equals (Vehical.class)" renvoie false, bien que l'objet Car appartienne à la classe Vehical mais il est catégorisé comme un type distinct.

Akash5288
la source