web-dev-qa-db-fra.com

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

J'utilise Eclipse pour générer .equals() et .hashCode(), et il existe une option intitulée "Utiliser 'instance de' pour comparer les types". La valeur par défaut est que cette option soit désélectionnée et utilise .getClass() pour comparer les types. Existe-t-il une raison pour laquelle je préférerais .getClass() à instanceof?

Sans utiliser instanceof:

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

Utiliser instanceof:

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

Je vérifie habituellement l’option instanceof, puis je supprime la vérification "if (obj == null)". (Il est redondant car les objets nuls échoueront toujours instanceof.) Y a-t-il une raison pour laquelle c'est une mauvaise idée?

166
Kip

Si vous utilisez instanceof, l'implémentation de equalsfinal préservera le contrat de symétrie de la méthode: x.equals(y) == y.equals(x). Si final semble restrictif, examinez soigneusement votre notion d'équivalence d'objet pour vous assurer que vos implémentations dominantes conservent pleinement le contrat établi par la classe Object.

95
erickson

Josh Bloch favorise votre approche:

La raison pour laquelle je préfère l'approche instanceof est que lorsque vous utilisez l'approche getClass, vous avez la restriction que les objets ne sont égaux qu'aux autres objets de la même classe, du même type d'exécution. Si vous étendez une classe en y ajoutant 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 equals. Si vous placez un membre de la super-classe dans une table de hachage en tant que clé, puis 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 this SO answer .

Effective Java chapitre couvre également cette question.

167
Michael Myers

Angelika Langers Les secrets de l'égal entre dans une longue et détaillée discussions sur quelques exemples courants et bien connus, notamment: par Josh Bloch et Barbara Liskov, découvrant quelques problèmes dans la plupart d'entre eux. Elle entre également dans le instanceof vs getClass. Quelques citations

Conclusions

Après avoir disséqué les quatre exemples d'implémentations de equals () choisis arbitrairement, 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 la comparaison de types mixtes entre des objets de super et de sous-classes à l'aide de l'opérateur instanceof, ou une classe peut traiter des objets de types différents comme non égaux au moyen du test getClass (). Les exemples ci-dessus montrent bien que les implémentations d'equals () à l'aide de getClass () sont généralement plus robustes que celles à l'aide d'instanceof.

L'instance de test n'est correcte 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 qu'elle ne peut ajouter que des fonctionnalités ou des champs sans importance 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, sont toujours conformes au contrat equals (); ils sont corrects et robustes. Elles sont toutefois très différentes sémantiquement des implémentations qui utilisent l'instanceof test. Les implémentations utilisant getClass () ne permettent pas la comparaison d'objets sous-super-classes, même lorsque la sous-classe n'ajoute aucun champ et ne souhaite même pas écraser equals (). Une telle extension de classe "triviale" serait, par exemple, l'ajout d'une méthode debug-print dans une sous-classe définie dans ce but "trivial". Si la superclasse interdit la comparaison de types mélangés via la vérification getClass (), l'extension triviale ne serait pas comparable à sa superclasse. Qu'il s'agisse ou non d'un problème dépend entièrement de la sémantique de la classe et du but de l'extension.

64

La raison d'utiliser getClass est d'assurer la propriété symétrique du contrat equals. À partir de JavaDocs:

Il est symétrique: pour toute valeur de référence non nulle x et y, x.equals (y) doit renvoyer true si et seulement si y.equals (x) renvoie true.

En utilisant instanceof, il est possible de ne pas être symétrique. Prenons l'exemple suivant: Chien étend Animal. equals de Animal effectue une vérification instanceof de Animal. Le chien equals effectue une vérification instanceof de Dog. Donner 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 respecter scrupuleusement le contrat d’égal, la symétrie doit être assurée et la classe doit donc être la même.

56
Steve Kuo

C'est un peu 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 violez le principe de substitution de Liskov.

Bloch a un autre conseil pertinent dans Effective Java Deuxième édition :

  • Rubrique 17: Conception et documentation pour héritage ou interdiction
26
McDowell

Corrigez-moi si je me trompe, mais getClass () sera utile si 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 parce que:

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
21
TraderJoeChicago

Si vous voulez vous assurer que seule cette classe correspondra, utilisez getClass() ==. Si vous voulez faire correspondre les sous-classes, alors instanceof est nécessaire.

En outre, instanceof ne sera pas comparé à un null mais pourra être comparé à un null. Donc, vous n'avez pas à le vérifier.

if ( ! (obj instanceof MyClass) ) { return false; }
5
Clint

Cela dépend si vous considérez si une sous-classe d'une classe donnée est égale à sa mère.

class LastName
{
(...)
}


class FamilyName
extends LastName
{
(..)
}

ici, je voudrais utiliser 'instanceof', parce que je veux qu'un nom soit comparé à un nom de famille

class Organism
{
}

class Gorilla extends Organism
{
}

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

5
Pierre

instanceof fonctionne pour les instances de la même classe o 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 instanceof 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 la suivante: "Un objet est égal à un autre uniquement s'ils appartiennent à la même classe", vous devez choisir "égal", ce qui, à mon avis, est la plupart des cas.

3
OscarRyz

Les deux méthodes ont leurs problèmes.

Si la sous-classe change l'identité, vous devez alors comparer leurs classes réelles. Sinon, vous violez la propriété symétrique. Par exemple, différents types de Persons 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 celles-ci doivent utiliser instanceof. Par exemple, si nous avons un tas d'objets Shape immuables, alors un Rectangle avec une longueur et une largeur de 1 doit être égal à l'unité Square.

En pratique, je pense que le premier cas est plus susceptible d'être vrai. Habituellement, le sous-classement est un élément fondamental de votre identité et être exactement comme vos parents, sauf que vous pouvez faire une petite chose ne vous rend pas égal.

2
James