java - ¿Alguna razón para preferir getClass() sobre instanceof al generar.equals()?
eclipse (11)
Ambos métodos tienen sus problemas.
Si la subclase cambia la identidad, entonces necesita comparar sus clases reales. De lo contrario, violas la propiedad simétrica. Por ejemplo, los diferentes tipos de Person
s no deben considerarse equivalentes, incluso si tienen el mismo nombre.
Sin embargo, algunas subclases no cambian de identidad y estas deben usar instanceof
. Por ejemplo, si tenemos un grupo de objetos Shape
inmutables, entonces un Rectangle
con longitud y ancho de 1 debe 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 creación de subclases es una parte fundamental de tu identidad y ser exactamente como tu padre, excepto que puedes hacer una pequeña cosa que no te hace igual.
Estoy usando Eclipse para generar .equals()
y .hashCode()
, y hay una opción con la etiqueta "Usar ''instanceof'' para comparar tipos". El valor predeterminado es que esta opción esté desmarcada y use .getClass()
para comparar tipos. ¿Hay alguna razón por la que prefiera .getClass()
sobre instanceof
?
Sin usar instanceof
:
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Usando instanceof
:
if (obj == null)
return false;
if (!(obj instanceof MyClass))
return false;
Por lo general, verifico la instanceof
opción, y luego voy y elimino la if (obj == null)
" if (obj == null)
". (Es redundante ya que los objetos nulos siempre fallarán). ¿Hay alguna razón por la cual sea una mala idea?
Angelika Langers Secrets of Equals entra en eso con una discusión larga y detallada de algunos ejemplos comunes y bien conocidos, incluidos Josh Bloch y Barbara Liskov, descubriendo un par de problemas en la mayoría de ellos. Ella también entra en la instanceof
vs getClass
. Algunas citas de ella
Conclusiones
Habiendo diseccionado los cuatro ejemplos arbitrariamente elegidos de implementaciones de iguales (), ¿qué concluimos?
En primer lugar, existen dos formas sustancialmente diferentes de realizar el control de la coincidencia de tipos en una implementación de iguales (). Una clase puede permitir la comparación de tipo mixto entre los objetos de superclase y de subclase por medio del operador instanceof, o una clase puede tratar objetos de tipo diferente como no igual mediante la prueba getClass (). Los ejemplos anteriores ilustraron muy bien que las implementaciones de equals () usando getClass () son generalmente más robustas que aquellas implementaciones que usan instanceof.
La prueba de instancia es correcta solo para las clases finales o si al menos el método igual a () es final en una superclase. Esto último esencialmente implica que ninguna subclase debe extender el estado de la superclase, pero solo puede agregar funcionalidad o campos que son irrelevantes para el estado y el comportamiento del objeto, como campos transitorios o estáticos.
Las implementaciones que usan 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 los objetos subclase con superclase, ni siquiera cuando la subclase no agrega ningún campo y no quiere anular Igual (). Tal extensión de clase "trivial" sería, por ejemplo, la adición de un método de depuración en una subclase definida exactamente para este propósito "trivial". Si la superclase prohíbe la comparación de tipo mixto a través de la comprobación getClass (), la extensión trivial no sería comparable a su superclase. Si este problema es o no depende completamente de la semántica de la clase y del propósito de la extensión.
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
Depende si considera si una subclase de una clase determinada es igual a su padre.
class LastName
{
(...)
}
class FamilyName
extends LastName
{
(..)
}
aquí usaría ''instanceof'', porque quiero que se compare un apellido con FamilyName
class Organism
{
}
class Gorilla extends Organism
{
}
aquí usaría ''getClass'', porque la clase ya dice que las dos instancias no son equivalentes.
En realidad, instancia de verificación donde un objeto pertenece a alguna jerarquía o no. ej .: el objeto del coche pertenece a la clase Vehical. Por lo tanto, la "nueva instancia de Vehical" (Vehical) vuelve verdadera. 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.
Esto es algo así como un debate religioso. Ambos enfoques tienen sus problemas.
- Use instanceof y nunca podrá agregar miembros significativos a subclases.
- Usa getClass y violas el principio de sustitución de Liskov.
Bloch tiene otro consejo relevante en Effective Java Second Edition :
- Elemento 17: diseño y documento para herencia o prohibirlo
La razón para usar getClass
es garantizar la propiedad simétrica del contrato equals
. De JavaDocs iguales:
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 que no sea simétrico. Considere el ejemplo: Dog extends Animal. Los equals
de Animal hacen una instanceof
verificación de Animal. El perro equals
una instanceof
verificación de perro. Give Animal a and Dog 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.
Si quiere asegurarse de que solo esa clase coincidirá, use getClass() ==
. Si quiere hacer coincidir subclases, entonces se necesita instanceof
.
Además, instanceof no coincidirá con un valor nulo, pero es seguro compararlo con un valor nulo. Entonces no tienes que anularlo.
if ( ! (obj instanceof MyClass) ) { return false; }
Si usa instanceof
, hacer que su implementación equals
final
conservará el contrato de simetría del método: x.equals(y) == y.equals(x)
. Si el final
parece restrictivo, examine cuidadosamente su noción de equivalencia de objeto para asegurarse de que sus implementaciones primordiales mantengan completamente el contrato establecido por la clase Object
.
Josh Bloch favorece su enfoque:
La razón por la que estoy a favor de la
instanceof
enfoque es que cuando usa el métodogetClass
, tiene la restricción de que los objetos son solo 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, luego 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 equals. Si coloca un miembro de la superclase en una tabla hash como la clave y luego lo busca utilizando una instancia de subclase, no lo encontrará, porque no son iguales.
Ver también esta respuesta SO .
El efectivo capítulo 3 de Java también lo cubre.
instanceof funciona para instences de la misma clase o sus 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 ambos instanceof 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.