Este problema es más evidente cuando tiene diferentes implementaciones de una interfaz, y para los propósitos de una colección en particular, solo le importa la vista a nivel de interfaz de los objetos. Por ejemplo, suponga que tiene una interfaz como esta:
public interface Person {
int getId();
}
La forma habitual de implementar hashcode()
e equals()
implementar clases tendría un código como este en el equals
método:
if (getClass() != other.getClass()) {
return false;
}
Esto causa problemas cuando mezclas implementaciones de Person
en a HashMap
. Si el HashMap
único se preocupa por la vista de nivel de interfaz de Person
, entonces podría terminar con duplicados que difieren solo en sus clases de implementación.
Puede hacer que este caso funcione utilizando el mismo equals()
método liberal para todas las implementaciones, pero luego corre el riesgo de equals()
hacer lo incorrecto en un contexto diferente (como comparar dos correos Person
electrónicos respaldados por registros de bases de datos con números de versión).
Mi intuición me dice que la igualdad debe definirse por colección en lugar de por clase. Al usar colecciones que dependen del pedido, puede usar una costumbre Comparator
para elegir el orden correcto en cada contexto. No existe un análogo para las colecciones basadas en hash. ¿Por qué es esto?
Solo para aclarar, esta pregunta es distinta de " ¿Por qué está .compareTo () en una interfaz mientras que .equals () está en una clase en Java? " Porque trata de la implementación de colecciones. compareTo()
y equals()
/ o hashcode()
ambos sufren el problema de la universalidad cuando se usan colecciones: no se pueden elegir diferentes funciones de comparación para diferentes colecciones. Entonces, para los propósitos de esta pregunta, la jerarquía de herencia de un objeto no importa en absoluto; lo único que importa es si la función de comparación se define por objeto o por colección.
fuente
Person
que implementen lo esperadoequals
y elhashCode
comportamiento. Entonces tendrías unHashMap<PersonWrapper, V>
. Este es un ejemplo en el que un enfoque de OOP puro no es elegante: no todas las operaciones en un objeto tienen sentido como método de ese objeto. Conjunto de javaObject
tipo es una amalgama de diferentes responsabilidades - sólo elgetClass
,finalize
ytoString
métodos parece remotamente justificable por las mejores prácticas de hoy en día.IEqualityComparer<T>
colección basada en hash. Si no especifica uno, utiliza una implementación predeterminada basada enObject.Equals
yObject.GetHashCode()
. 2) La anulación de la OMIEquals
en un tipo de referencia mutable rara vez es una buena idea. De esa manera, la igualdad predeterminada es bastante estricta, pero puede usar una regla de igualdad más relajada cuando la necesite a través de una costumbreIEqualityComparer<T>
.Respuestas:
Este diseño a veces se conoce como "Igualdad universal", es la creencia de que si dos cosas son iguales o no es una propiedad universal.
Además, la igualdad es una propiedad de dos objetos, pero en OO, siempre se llama a un método en un solo objeto , y ese objeto decide únicamente cómo manejar esa llamada al método. Entonces, en un diseño como el de Java, donde la igualdad es una propiedad de uno de los dos objetos que se comparan, ni siquiera es posible garantizar algunas propiedades básicas de igualdad como la simetría (
a == b
⇔b == a
), porque en el primer caso, el método se llamaa
y, en el segundo caso, se llamab
y, debido a los principios básicos de OO, esa
decisión exclusiva (en el primer caso) ob
La decisión de (en el segundo caso) si se considera igual o no al otro. La única forma de ganar simetría es hacer que los dos objetos cooperen, pero si no lo hacen ... mala suerte.Una solución sería hacer que la igualdad no sea una propiedad de un objeto, sino una propiedad de dos objetos o una propiedad de un tercer objeto. Esa última opción también resuelve el problema de la igualdad universal, porque si hace de la igualdad una propiedad de un tercer objeto de "contexto", entonces puede imaginar tener diferentes
EqualityComparer
objetos para diferentes contextos.Este es el diseño elegido para Haskell, por ejemplo, con la
Eq
clase de tipos. También es el diseño elegido por algunas bibliotecas de Scala de terceros (ScalaZ, por ejemplo), pero no el núcleo de Scala o la biblioteca estándar, que utiliza la igualdad universal para la compatibilidad con la plataforma host subyacente.Es, curiosamente, también el diseño elegido con las interfaces
Comparable
/ JavaComparator
. Los diseñadores de Java claramente estaban conscientes del problema, pero por alguna razón solo lo resolvieron para ordenar, pero no para la igualdad (o hashing).Entonces, en cuanto a la pregunta
La respuesta es "No sé". Claramente, los diseñadores de Java eran conscientes del problema, como lo demuestra la existencia de
Comparator
, pero obviamente no lo consideraban un problema para la igualdad y el hash. Otros idiomas y bibliotecas toman diferentes decisiones.fuente
The methods equals and hashCode are declared for the benefit of hashtables such as java.util.Hashtable
es decir, ambosequals
yhashCode
fueron introducidos comoObject
métodos por los desarrolladores de Java solo por el bienHashtable
: no hay una noción de UE o nada silimar en ninguna parte de la especificación, y la cita es lo suficientemente clara para mí; si no fuera por elHashtable
,equals
probablemente habría estado en una interfaz comoComparable
. Como tal, aunque antes creía que su respuesta era correcta, ahora la considero sin fundamento.clone
: originalmente era un operador , no un método (ver la especificación del lenguaje Oak), cita:The unary operator clone is applied to an object. (...) The clone operator is normally used inside new to clone the prototype of some class, before applying the initializers (constructors)
los tres operadores similares a palabras clave eraninstanceof new clone
(sección 8.1, operadores). Supongo que esa es la razón real (histórica) delclone
/Cloneable
mess:Cloneable
fue simplemente una invención posterior, y elclone
código existente se modificó con él.La verdadera respuesta a
es, cita cortesía de Josh Bloch :
El problema reside únicamente en la historia de Java, al igual que con otros asuntos similares, por ejemplo
.clone()
vsCloneable
.tl; dr
es por razones históricas principalmente; El comportamiento / abstracción actual se introdujo en JDK 1.0 y no se solucionó más adelante porque era prácticamente imposible mantener la compatibilidad con el código hacia atrás.
Primero, resumamos un par de hechos conocidos de Java:
Hashtable
,.hashCode()
y.equals()
se implementaron en JDK 1.0, ( Hashtable )Comparable
/Comparator
fue introducido en JDK 1.2 ( Comparable ),Ahora, sigue:
.hashCode()
e.equals()
interfaces distintas mientras se mantenía la compatibilidad con versiones anteriores después de que las personas se dieron cuenta de que hay mejores abstracciones que ponerlas en superobjeto, porque, por ejemplo, todos y cada uno de los programadores de Java de 1.2 sabían que todosObject
los tenían, y tenían permanecer allí físicamente para proporcionar compatibilidad con el código compilado (JVM) también, y agregar una interfaz explícita a cadaObject
subclase que realmente los implementara haría que este desastre fuera igual (sic!) aClonable
uno ( Bloch discute por qué Cloneable apesta , también discutido en, por ejemplo, EJ 2nd y muchos otros lugares, incluido SO),Ahora, puedes preguntar "¿qué
Hashtable
tiene todo esto"?La respuesta es:
hashCode()
/equals()
contrato y habilidades de diseño de lenguaje no tan buenas de los principales desarrolladores de Java en 1995/1996.Cita de Java 1.0 Language Spec, con fecha de 1996 - 4.3.2 The Class
Object
, p.41:(tenga en cuenta esta declaración exacta se ha cambiado en versiones posteriores, decir, cita:
The method hashCode is very useful, together with the method equals, in hashtables such as java.util.HashMap.
, por lo que es imposible hacer la directaHashtable
-hashCode
-equals
conexión sin leer JLS históricos!)El equipo de Java decidió que querían una buena colección de estilo de diccionario, y crearon
Hashtable
(buena idea hasta ahora), pero querían que el programador pudiera usarla con la menor curva de código / aprendizaje posible (¡vaya! ¡Problemas entrantes!) - y, dado que todavía no había genéricos [después de todo, es JDK 1.0], eso significaría que cadaObject
puestoHashtable
tendría que implementar explícitamente alguna interfaz (y las interfaces todavía estaban en su inicio en ese momento ... ¡Comparable
aún no !) , haciendo que esto sea un elemento disuasorio para usarlo para muchos, oObject
tendría que implementar implícitamente algún método de hash.Obviamente, optaron por la solución 2, por los motivos descritos anteriormente. Sí, ahora sabemos que estaban equivocados. ... es fácil ser inteligente en retrospectiva. risita
Ahora,
hashCode()
requiere que cada objeto que lo tenga tenga unequals()
método distinto , por lo que era bastante obvio que tambiénequals()
tenía que colocarseObject
.Desde los predeterminados implementaciones de esos métodos en válida
a
yb
Object
s son esencialmente inútiles por ser redundante (haciendoa.equals(b)
igual aa==b
ya.hashCode() == b.hashCode()
aproximadamente igual aa==b
también, a menos quehashCode
y / oequals
está anulado, o GC cientos de miles deObject
s durante el ciclo de vida de la aplicación 1 ) , es seguro decir que se proporcionaron principalmente como una medida de respaldo y por conveniencia de uso. Así es exactamente como llegamos al conocido hecho de que siempre anulamos tanto.equals()
&.hashCode()
si tiene la intención de comparar realmente los objetos o almacenarlos en hash. Reemplazar solo uno de ellos sin el otro es una buena manera de atornillar su código (mediante resultados de comparación perversos o valores de colisión de cubo increíblemente altos), y entenderlo es una fuente de confusión y errores constantes para principiantes (busque SO para ver para ti) y molestias constantes para los más experimentados.Además, tenga en cuenta que, aunque C # trata con iguales y hashcode de una manera un poco mejor, Eric Lippert mismo afirma que cometieron casi el mismo error con C # que Sun hizo con Java años antes del inicio de C # :
1, por supuesto,
Object#hashCode
aún puede colisionar, pero se necesita un poco de esfuerzo para hacerlo, consulte: http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6809470 e informes de errores vinculados para obtener más detalles; /programming/1381060/hashcode-uniqueness/1381114#1381114 cubre este tema más en profundidad.fuente
Object
el diseño; hashability fue.