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

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?

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étodo getClass , 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.

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.

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.

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

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.

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; } 

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.

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.

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.