¿Alguna razón para preferir getClass () sobre instanceof al generar .equals ()?

174

Estoy usando Eclipse para generar .equals()y .hashCode(), y hay una opción etiquetada "Usar 'instancia de' para comparar tipos". El valor predeterminado es que esta opción no esté marcada y se use .getClass()para comparar tipos. ¿Hay alguna razón yo preferiría .getClass()más instanceof?

Sin usar instanceof:

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

Utilizando instanceof:

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

Por lo general, verifico la instanceofopción y luego entro y elimino el " if (obj == null)" cheque. (Es redundante ya que los objetos nulos siempre fallarán instanceof). ¿Hay alguna razón que sea una mala idea?

Dormir
fuente
1
La expresión x instanceof SomeClasses falsa si xes null. Por lo tanto, la segunda sintaxis no necesita la comprobación nula.
rds
55
@rds Sí, el párrafo inmediatamente después del fragmento de código también dice esto. Está en el fragmento de código porque eso es lo que genera Eclipse.
Kip

Respuestas:

100

Si se utiliza instanceof, por lo que su equalsaplicación finalva a conservar el contrato de simetría del método: x.equals(y) == y.equals(x). Si finalparece restrictivo, examine cuidadosamente su noción de equivalencia de objetos para asegurarse de que sus implementaciones primordiales mantengan completamente el contrato establecido por la Objectclase.

erickson
fuente
exactamente eso me vino a la mente cuando leí la cita de josh bloch arriba. +1 :)
Johannes Schaub - litb
13
Definitivamente la decisión clave. la simetría debe aplicarse, y la instancia de hace que sea muy fácil ser asimétrico accidentalmente
Scott Stanchfield
También agregaría que "si finalparece restrictivo" (en ambos equalsy hashCode) luego usar la getClass()igualdad en lugar de instanceofpara preservar los requisitos de simetría y transitividad del equalscontrato.
Shadow Man
176

Josh Bloch favorece su enfoque:

La razón por la que estoy a favor del instanceofenfoque es que cuando usa el getClassenfoque, tiene la restricción de que los objetos solo son iguales a otros objetos de la misma clase, el mismo tipo de tiempo de ejecución. Si extiende una clase y le agrega un par de métodos inocuos, verifique si algún objeto de la subclase es igual a un objeto de la superclase, incluso si los objetos son iguales en todos los aspectos importantes, obtendrá el sorprendente respuesta de que no son iguales. De hecho, esto viola una interpretación estricta del principio de sustitución de Liskov y puede conducir a un comportamiento muy sorprendente. En Java, es particularmente importante porque la mayoría de las colecciones (HashTable, etc.) se basan en el método igual. Si coloca un miembro de la superclase en una tabla hash como clave y luego lo busca utilizando una instancia de subclase, no lo encontrará, porque no son iguales.

Vea también esta respuesta SO .

El eficaz capítulo 3 de Java también cubre esto.

Michael Myers
fuente
18
+1 ¡Todos los que hagan Java deberían leer ese libro al menos 10 veces!
André
16
El problema con el enfoque de instancia de es que rompe la propiedad "simétrica" ​​de Object.equals () en que es posible que x.equals (y) == verdadero pero y.equals (x) == falso si x.getClass ()! = y.getClass (). Prefiero no romper esta propiedad a menos que sea absolutamente necesario (es decir, anular equals () para objetos administrados o proxy).
Kevin Sitze
8
El enfoque de instancia de es apropiado cuando, y solo cuando, la clase base define lo que debería significar la igualdad entre los objetos de la subclase. El uso getClassno viola el LSP, ya que el LSP simplemente se relaciona con lo que se puede hacer con las instancias existentes, no con qué tipos de instancias se pueden construir. La clase devuelta por getClasses una propiedad inmutable de una instancia de objeto. El LSP no implica que deba ser posible crear una subclase donde esa propiedad indique cualquier clase que no sea la que la creó.
supercat
66

Angelika Langers Secrets of equals se mete en eso con una discusión larga y detallada de algunos ejemplos comunes y conocidos, incluidos Josh Bloch y Barbara Liskov, descubriendo un par de problemas en la mayoría de ellos. Ella también se mete en el instanceoffrente getClass. Alguna cita de ella

Conclusiones

Después de analizar los cuatro ejemplos elegidos arbitrariamente de implementaciones de equals (), ¿qué concluimos?

En primer lugar: hay dos formas sustancialmente diferentes de realizar la verificación de la coincidencia de tipos en una implementación de equals (). Una clase puede permitir la comparación de tipos mixtos entre objetos de superclases y subclases mediante el operador instanceof, o una clase puede tratar objetos de diferente tipo como no iguales mediante la prueba getClass (). Los ejemplos anteriores ilustran muy bien que las implementaciones de equals () usando getClass () son generalmente más robustas que aquellas implementaciones que usan instanceof.

La instancia de prueba es correcta solo para las clases finales o si al menos el método equals () es final en una superclase. Esto último implica esencialmente que ninguna subclase debe extender el estado de la superclase, sino que solo puede agregar funcionalidades o campos que son irrelevantes para el estado y el comportamiento del objeto, como los campos transitorios o estáticos.

Las implementaciones que utilizan la prueba getClass (), por otro lado, siempre cumplen con el contrato equals (); Son correctos y robustos. Sin embargo, son semánticamente muy diferentes de las implementaciones que usan la instancia de prueba. Las implementaciones que usan getClass () no permiten la comparación de objetos de subclase con superclase, ni siquiera cuando la subclase no agrega ningún campo y ni siquiera quisiera anular equals (). Tal extensión de clase "trivial" sería, por ejemplo, la adición de un método de impresión de depuración en una subclase definida exactamente para este propósito "trivial". Si la superclase prohíbe la comparación de tipo mixto mediante la comprobación getClass (), la extensión trivial no sería comparable a su superclase. Si este es un problema o no, depende completamente de la semántica de la clase y del propósito de la extensión.

Johannes Schaub - litb
fuente
61

La razón para usar getClasses garantizar la propiedad simétrica del equalscontrato. De igual a JavaDocs:

Es simétrico: para cualquier valor de referencia no nulo x e y, x.equals (y) debería devolver verdadero si y solo si y.equals (x) devuelve verdadero.

Al usar instanceof, es posible no ser simétrico. Considere el ejemplo: Perro extiende Animal. Animal's equalshace un instanceofchequeo de Animal. El perro equalshace un instanceofchequeo del perro. Dar Animal a y Perro d (con otros campos iguales):

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

Esto viola la propiedad simétrica.

Para seguir estrictamente el contrato de igualdad, se debe garantizar la simetría y, por lo tanto, la clase debe ser la misma.

Steve Kuo
fuente
8
Esa es la primera respuesta clara y concisa para esta pregunta. Una muestra de código vale más que mil palabras.
Johnride
Estaba revisando todas las otras respuestas detalladas que utilizaste para salvar el día. Simple, conciso, elegante y la mejor respuesta de esta pregunta.
1
Existe el requisito de simetría como usted mencionó. Pero también existe el requisito de "transitividad". Si a.equals(c)y b.equals(c), entonces a.equals(b)(el enfoque ingenuo de hacer Dog.equalssolo return super.equals(object)cuando !(object instanceof Dog)pero verificando los campos adicionales cuando se trata de una instancia de Dog no violaría la simetría pero violaría la transitividad)
Shadow Man
Para estar seguro, se puede utilizar una getClass()comprobación de igualdad, o puede utilizar una instanceofverificación si hacer sus equalsy hashCodemétodos final.
Shadow Man
26

Esto es algo de un debate religioso. Ambos enfoques tienen sus problemas.

  • Use instanceof y nunca podrá agregar miembros significativos a las subclases.
  • Usa getClass y violas el principio de sustitución de Liskov.

Bloch tiene otro consejo relevante en Effective Java Second Edition :

  • Punto 17: Diseñe y documente para herencia o prohíbala
McDowell
fuente
1
Nada sobre el uso getClassviolaría el LSP, a menos que la clase base documentara específicamente un medio por el cual debería ser posible hacer instancias de diferentes subclases que se comparen igual. ¿Qué violación de LSP ves?
supercat
Tal vez eso debería reformularse como Use getClass y deje los subtipos en peligro de violaciones de LSP. Bloch tiene un ejemplo en Effective Java, también discutido aquí . Su lógica es en gran medida que puede dar lugar a un comportamiento sorprendente para los desarrolladores que implementan subtipos que no agregan estado. Entiendo su punto: la documentación es clave.
McDowell
2
La única vez que dos instancias de clases distintas deberían informarse a sí mismas es igual si heredan de una clase o interfaz base común que define lo que significa igualdad [una filosofía que Java aplicó, dudosamente en mi humilde opinión, a algunas interfaces de colección]. A menos que el contrato de la clase base diga que getClass()no debe considerarse significativo más allá del hecho de que la clase en cuestión es convertible a la clase base, el rendimiento de getClass()debe considerarse como cualquier otra propiedad que debe coincidir para que las instancias sean iguales.
supercat
1
@eyalzba Donde instanceof_ se usa si una subclase agrega miembros al contrato igual, esto violaría el requisito de igualdad simétrica . El uso de las subclases getClass nunca puede ser igual al tipo principal. No es que deba anular iguales de todos modos, pero esto es una compensación si lo hace.
McDowell
2
"Diseñe y documente la herencia o prohíbala" Creo que es muy importante. Tengo entendido que el único momento en que debe anular es igual a si está creando un tipo de valor inmutable, en cuyo caso la clase debería declararse final. Como no habrá herencia, puede implementar iguales según sea necesario. En los casos en los que permite la herencia, los objetos deben compararse por igualdad de referencia.
TheSecretSquad
23

Corrígeme si estoy equivocado, pero getClass () será útil cuando quieras asegurarte de que tu instancia NO sea una subclase de la clase con la que estás comparando. Si usa instanceof en esa situación, NO puede saberlo porque:

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
TraderJoeChicago
fuente
Para mí, esta es la razón
karlihnos
5

Si desea asegurarse de que solo esa clase coincida, use getClass() ==. Si desea hacer coincidir las subclases, entonces instanceofes necesario.

Además, instanceof no coincidirá con un nulo, pero es seguro compararlo con un nulo. Por lo tanto, no tiene que comprobarlo nulo.

if ( ! (obj instanceof MyClass) ) { return false; }
Clint
fuente
Re su segundo punto: ya lo mencionó en la pregunta. "Por lo general, verifico la opción instanceof, y luego entro y elimino la comprobación 'if (obj == null)'".
Michael Myers
5

Depende si considera si una subclase de una clase dada es igual a su padre.

class LastName
{
(...)
}


class FamilyName
extends LastName
{
(..)
}

aquí usaría 'instanceof', porque quiero que se compare un Apellido con un Nombre de Familia

class Organism
{
}

class Gorilla extends Organism
{
}

aquí usaría 'getClass', porque la clase ya dice que las dos instancias no son equivalentes.

Pierre
fuente
3

instanceof funciona para instencias de la misma clase o subclases

Puede usarlo para probar si un objeto es una instancia de una clase, una instancia de una subclase o una instancia de una clase que implementa una interfaz particular.

ArryaList y RoleList son instancias de List

Mientras

getClass () == o.getClass () será verdadero solo si ambos objetos (this y o) pertenecen exactamente a la misma clase.

Entonces, dependiendo de lo que necesite comparar, podría usar uno u otro.

Si su lógica es: "Un objeto es igual a otro solo si ambos son de la misma clase", debe elegir "igual", que creo que es la mayoría de los casos.

OscarRyz
fuente
3

Ambos métodos tienen sus problemas.

Si la subclase cambia la identidad, entonces necesita comparar sus clases reales. De lo contrario, viola la propiedad simétrica. Por ejemplo, diferentes tipos de Persons no deben considerarse equivalentes, incluso si tienen el mismo nombre.

Sin embargo, algunas subclases no cambian de identidad y deben usarse instanceof. Por ejemplo, si tenemos un montón de Shapeobjetos inmutables , entonces un Rectanglecon longitud y ancho de 1 debería ser igual a la unidad Square.

En la práctica, creo que el primer caso es más probable que sea cierto. Por lo general, la subclasificación es una parte fundamental de su identidad y ser exactamente como su padre, excepto que puede hacer una pequeña cosa, no lo hace igual.

James
fuente
-1

En realidad, instancia de comprobar dónde pertenece un objeto a alguna jerarquía o no. Ej: El objeto del automóvil pertenece a la clase Vehical. Entonces, "nueva instancia de Car () de Vehical" devuelve verdadero. Y "new Car (). GetClass (). Equals (Vehical.class)" devuelve falso, aunque el objeto Car pertenece a la clase Vehical pero está categorizado como un tipo separado.

Akash5288
fuente