La verdadera respuesta a
¿Por qué hay una Comparator
interfaz pero no Hasher
y Equator
?
es, cita cortesía de Josh Bloch :
Las API de Java originales se realizaron muy rápidamente en un plazo ajustado para cumplir con una ventana de mercado de cierre. El equipo original de Java hizo un trabajo increíble, pero no todas las API son perfectas.
El problema reside únicamente en la historia de Java, al igual que con otros asuntos similares, por ejemplo .clone()
vs Cloneable
.
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:
- Java, desde el principio hasta el día de hoy, era orgullosamente compatible con versiones anteriores, y requería que las API heredadas aún se admitieran en versiones más nuevas,
- como tal, casi todos y cada uno de los idiomas introducidos con JDK 1.0 vivieron hasta nuestros días,
Hashtable
, .hashCode()
y .equals()
se implementaron en JDK 1.0, ( Hashtable )
Comparable
/ Comparator
fue introducido en JDK 1.2 ( Comparable ),
Ahora, sigue:
- era virtualmente imposible y sin sentido adaptar
.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 todos Object
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 cada Object
subclase que realmente los implementara haría que este desastre fuera igual (sic!) a Clonable
uno ( Bloch discute por qué Cloneable apesta , también discutido en, por ejemplo, EJ 2nd y muchos otros lugares, incluido SO),
- los dejaron allí para que la generación futura tenga una fuente constante de WTF.
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:
Los métodos equals
y hashCode
se declaran en beneficio de java.util.Hashtable
tablas hash como (§21.7). El método igual define una noción de igualdad de objetos, que se basa en el valor, no en la referencia, en la comparación.
(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 directa Hashtable
- 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 cada Object
puesto Hashtable
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, o Object
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 un equals()
método distinto , por lo que era bastante obvio que también equals()
tenía que colocarse Object
.
Desde los predeterminados implementaciones de esos métodos en válida a
y b
Object
s son esencialmente inútiles por ser redundante (haciendo a.equals(b)
igual a a==b
y a.hashCode() == b.hashCode()
aproximadamente igual a a==b
también, a menos que hashCode
y / o equals
está anulado, o GC cientos de miles de Object
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 # :
Pero, ¿por qué debería ser el caso de que cada objeto pueda ser hash para insertarse en una tabla hash? Parece una cosa extraña requerir que cada objeto pueda hacer. Creo que si estuviéramos rediseñando el sistema de tipos desde cero hoy, el hash podría hacerse de manera diferente, tal vez con una IHashable
interfaz. Pero cuando se diseñó el sistema de tipos CLR no había tipos genéricos y, por lo tanto, se necesitaba una tabla hash de propósito general para poder almacenar cualquier objeto.
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.
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.