He leído este artículo: Cómo escribir un método de igualdad en Java .
Básicamente, proporciona una solución para un método equals () que admite la herencia:
Point2D twoD = new Point2D(10, 20);
Point3D threeD = new Point3D(10, 20, 50);
twoD.equals(threeD); // true
threeD.equals(twoD); // true
¿Pero es una buena idea? Estas dos instancias parecen ser iguales pero pueden tener dos códigos hash diferentes. ¿No está un poco mal?
Creo que esto se lograría mejor lanzando los operandos en su lugar.
java
c#
scala
comparison
Wes
fuente
fuente
z
coordenada cero podría ser una convención útil para algunas aplicaciones (me vienen a la mente los primeros sistemas CAD que manejan datos heredados). Pero es una convención arbitraria. Los planos en espacios con 3 o más dimensiones pueden tener orientaciones arbitrarias ... es lo que hace interesantes los problemas interesantes.Respuestas:
Esto no debería ser igualdad porque rompe la transitividad . Considere estas dos expresiones:
Como la igualdad es transitiva, esto debería significar que la siguiente expresión también es verdadera:
Pero, por supuesto, no lo es.
Por lo tanto, su idea de conversión es correcta: espere que en Java, transmitir simplemente signifique emitir el tipo de referencia. Lo que realmente quieres aquí es un método de conversión que cree un nuevo
Point2D
objeto a partir de unPoint3D
objeto. Esto también haría que la expresión sea más significativa:fuente
(10, 20, 50)
igual(10, 20, 60)
está bien. Solo nos importa10
y20
.Point2D
tener unprojectXYZ()
método para proporcionar unaPoint3D
representación de sí mismo? En otras palabras, ¿las implementaciones deberían conocerse?Point2D
parece más simple ya que proyectar puntos 2D requiere definir primero su plano en el espacio 3D. Si el punto 2D sabe que es plano, entonces ya es un punto 3D. Si no lo hace, no puede proyectarse. Me recuerda de Abbott Planilandia .Plane3D
objeto, que definirá un plano en el espacio 3D, ese plano puede tener unlift
método (2D-> 3D se levanta, no se proyecta) que aceptará unPoint2D
y un número para el "tercer eje "- distancia del avión a lo largo del avión normal. Para facilitar su uso, puede definir los planos comunes como constantes estáticas, para que pueda hacer cosas comoPlane3D.XY.lift(new Point2D(10, 20), 50).equals(new Point3D(10, 20, 50))
Me alejo de leer el artículo pensando en la sabiduría de Alan J. Perlis:
El hecho de que acertar con la "igualdad" es el tipo de problema que mantiene despierto a Martin Ordersky, inventor de Scala por la noche, debería hacer una pausa sobre si anular
equals
en un árbol de herencia es una buena idea.Lo que sucede cuando tenemos mala suerte
ColoredPoint
es que nuestra geometría falla porque usamos la herencia para proliferar los tipos de datos en lugar de crear uno bueno. Esto a pesar de tener que regresar y modificar el nodo raíz del árbol de herencia para queequals
funcione. ¿Por qué no basta con añadir unaz
y unacolor
paraPoint
?La buena razón sería eso
Point
yColoredPoint
operar en diferentes dominios ... al menos si esos dominios nunca se mezclan. Sin embargo, si ese es el caso, no necesitamos anularequals
. CompararColoredPoint
yPoint
por igualdad solo tiene sentido en un tercer dominio donde se les permite mezclarse. Y en ese caso, probablemente sea mejor tener la "igualdad" adaptada a ese tercer dominio en lugar de tratar de aplicar la semántica de igualdad de uno u otro o de ambos dominios no mezclados. En otras palabras, la "igualdad" debe definirse localmente en el lugar donde tenemos el lodo que fluye desde ambos lados porque es posible que no queramosColoredPoint.equals(pt)
fallar contra las instancias,Point
incluso si el autorColoredPoint
pensó que era una buena idea hace seis meses a las 2 a.m. .fuente
Cuando los antiguos dioses de la programación inventaban la programación orientada a objetos con clases, decidieron cuando se trataba de composición y herencia tener dos relaciones para un objeto: "es un" y "tiene un".
Esto resolvió parcialmente el problema de que las subclases fueran diferentes a las clases primarias, pero las hizo utilizables sin romper el código. Debido a que una instancia de subclase "es un" objeto de superclase y puede sustituirse directamente por él, a pesar de que la subclase tiene más funciones miembro o miembros de datos, el "tiene un" garantiza que realizará todas las funciones del padre y tendrá todos sus miembros. Entonces, se podría decir que un Point3D "es un" Punto, y un Point2D "es un" Punto si ambos heredan de Point. Además, un Point3D podría ser una subclase de Point2D.
Sin embargo, la igualdad entre clases es un problema específico del dominio, y el ejemplo anterior es ambiguo en cuanto a lo que el programador necesita para que el programa funcione correctamente. En general, se siguen las reglas del dominio matemático y los valores de los datos generarían igualdad si limita el alcance de la comparación a solo en este caso dos dimensiones, pero no si compara todos los miembros de datos.
Entonces obtienes una tabla de reducciones de igualdad:
Por lo general, elige las reglas más estrictas que pueda que aún realicen todas las funciones necesarias en su dominio problemático. Las pruebas de igualdad incorporadas para los números están diseñadas para ser tan restrictivas como pueden ser para fines matemáticos, pero el programador tiene muchas formas de evitarlo si ese no es el objetivo, incluido el redondeo arriba / abajo, truncamiento, gt, lt, etc. . Los objetos con marcas de tiempo a menudo se comparan por su tiempo de generación, por lo que cada instancia debe ser única para que las comparaciones sean muy específicas.
El factor de diseño en este caso es determinar formas eficientes de comparar objetos. A veces, una comparación recursiva de todos los miembros de datos de objetos es lo que debe hacer, y eso puede ser muy costoso si tiene muchos objetos con muchos miembros de datos. Las alternativas son solo comparar valores de datos relevantes, o hacer que el objeto genere un valor hash de sus miembros de datos interesados para una comparación rápida con otros objetos similares, mantener las colecciones ordenadas y podadas para hacer comparaciones más rápidas y menos intensivas en CPU, y quizás permitir objetos que son idénticos en los datos que se seleccionarán y se colocará un puntero duplicado a un solo objeto en su lugar.
fuente
La regla es, cada vez que anula
hashcode()
, anulaequals()
, y viceversa. Si esta es una buena idea o no, depende del uso previsto. Personalmente, usaría un método diferente (isLike()
o similar) para lograr el mismo efecto.fuente
A menudo es útil para personas que no se enfrentan al público clases tengan un método de prueba de equivalencia que permita que los objetos de diferentes tipos se consideren "iguales" si representan la misma información, pero debido a que Java no permite ningún medio por el cual las clases puedan hacerse pasar por cada uno. otro, a menudo es bueno tener un único tipo de contenedor orientado al público en los casos en que sea posible tener objetos equivalentes con diferentes representaciones.
Por ejemplo, considere una clase que encapsula una matriz de
double
valores 2D inmutable . Si un método externo solicita una matriz de identidad de tamaño 1000, un segundo solicita una matriz diagonal y pasa una matriz que contiene 1000 unidades, y un tercero pide una matriz 2D y pasa una matriz 1000x1000 donde los elementos en la diagonal primaria son todos 1.0 y todos los demás son cero, los objetos dados a las tres clases pueden usar diferentes almacenes de respaldo internamente [el primero tiene un solo campo para el tamaño, el segundo tiene una matriz de mil elementos y el tercero tiene mil matrices de 1000 elementos] pero deben informarse entre sí como equivalentes [ya que los tres encapsulan una matriz inmutable de 1000x1000 con unos en diagonal y ceros en cualquier otro lugar].Más allá del hecho de que oculta la existencia de distintos tipos de tiendas de respaldo, el contenedor también será útil para facilitar las comparaciones, ya que la verificación de la equivalencia de los artículos generalmente será un proceso de varios pasos. Pregunte al primer elemento si sabe si es igual al segundo; si no sabe, pregunte al segundo si sabe si es igual al primero. Si ninguno de los objetos lo sabe, pregúntele a cada matriz sobre el contenido de sus elementos individuales [uno podría agregar otras comprobaciones antes de decidir hacer la ruta de comparación de elementos individuales larga y lenta].
Tenga en cuenta que el método de prueba de equivalencia para cada objeto en este escenario necesitaría devolver un valor de tres estados ("Sí, soy equivalente", "No, no soy equivalente" o "No sé"), entonces el método normal "igual" no sería adecuado. Si bien cualquier objeto podría simplemente responder "No sé" cuando se le pregunta sobre cualquier otro, agregar lógica a, por ejemplo, una matriz diagonal que no molestaría en preguntar a ninguna matriz de identidad o matriz diagonal sobre cualquier elemento fuera de la diagonal principal, aceleraría enormemente las comparaciones entre tales tipos.
fuente