Entrar en el área gris de "tema encendido / apagado", pero necesario para eliminar la confusión con respecto a la sugerencia de Oscar Reyes de que más colisiones de hash es algo bueno porque reduce la cantidad de elementos en el HashMap. Puede que no entienda lo que dice Oscar, pero no parece que sea el único: kdgregory, delfuego, Nash0, y parece que todos compartimos la misma (mala) comprensión.
Si entiendo lo que dice Oscar sobre la misma clase con el mismo código hash, está proponiendo que solo se inserte una instancia de una clase con un código hash determinado en el HashMap. Por ejemplo, si tengo una instancia de SomeClass con un código hash de 1 y una segunda instancia de SomeClass con un código hash de 1, solo se inserta una instancia de SomeClass.
El ejemplo de pastebin de Java en http://pastebin.com/f20af40b9 parece indicar que lo anterior resume correctamente lo que propone Oscar.
Independientemente de cualquier comprensión o malentendido, lo que sucede es que diferentes instancias de la misma clase no se insertan solo una vez en el HashMap si tienen el mismo código hash, no hasta que se determine si las claves son iguales o no. El contrato de código hash requiere que los objetos iguales tengan el mismo código hash; sin embargo, no requiere que los objetos desiguales tengan diferentes códigos hash (aunque esto puede ser deseable por otras razones) [1].
A continuación se muestra el ejemplo pastebin.com/f20af40b9 (al que Oscar se refiere al menos dos veces), pero modificado ligeramente para usar aserciones JUnit en lugar de líneas de impresión. Este ejemplo se utiliza para respaldar la propuesta de que los mismos códigos hash causan colisiones y cuando las clases son las mismas, solo se crea una entrada (por ejemplo, solo una cadena en este caso específico):
@Test
public void shouldOverwriteWhenEqualAndHashcodeSame() {
String s = new String("ese");
String ese = new String("ese");
// same hash right?
assertEquals(s.hashCode(), ese.hashCode());
// same class
assertEquals(s.getClass(), ese.getClass());
// AND equal
assertTrue(s.equals(ese));
Map map = new HashMap();
map.put(s, 1);
map.put(ese, 2);
SomeClass some = new SomeClass();
// still same hash right?
assertEquals(s.hashCode(), ese.hashCode());
assertEquals(s.hashCode(), some.hashCode());
map.put(some, 3);
// what would we get?
assertEquals(2, map.size());
assertEquals(2, map.get("ese"));
assertEquals(3, map.get(some));
assertTrue(s.equals(ese) && s.equals("ese"));
}
class SomeClass {
public int hashCode() {
return 100727;
}
}
Sin embargo, el código hash no es la historia completa. Lo que el ejemplo de pastebin ignora es el hecho de que ambos s
y ese
son iguales: ambos son la cadena "ese". Por lo tanto, insertar u obtener el contenido del mapa usando s
o ese
o "ese"
como clave son todos equivalentes porques.equals(ese) && s.equals("ese")
.
Una segunda prueba demuestra que es erróneo concluir que códigos hash idénticos en la misma clase es la razón por la que la clave -> valor s -> 1
se sobrescribe ese -> 2
cuando map.put(ese, 2)
se llama en la prueba uno. En la prueba dos, s
y ese
todavía tienen el mismo código hash (verificado por assertEquals(s.hashCode(), ese.hashCode());
) Y son de la misma clase. Sin embargo, s
y ese
son MyString
instancias en esta prueba, no String
instancias de Java , con la única diferencia relevante para esta prueba siendo los iguales: String s equals String ese
en la prueba uno anterior, mientras que MyStrings s does not equal MyString ese
en la prueba dos:
@Test
public void shouldInsertWhenNotEqualAndHashcodeSame() {
MyString s = new MyString("ese");
MyString ese = new MyString("ese");
// same hash right?
assertEquals(s.hashCode(), ese.hashCode());
// same class
assertEquals(s.getClass(), ese.getClass());
// BUT not equal
assertFalse(s.equals(ese));
Map map = new HashMap();
map.put(s, 1);
map.put(ese, 2);
SomeClass some = new SomeClass();
// still same hash right?
assertEquals(s.hashCode(), ese.hashCode());
assertEquals(s.hashCode(), some.hashCode());
map.put(some, 3);
// what would we get?
assertEquals(3, map.size());
assertEquals(1, map.get(s));
assertEquals(2, map.get(ese));
assertEquals(3, map.get(some));
}
/**
* NOTE: equals is not overridden so the default implementation is used
* which means objects are only equal if they're the same instance, whereas
* the actual Java String class compares the value of its contents.
*/
class MyString {
String i;
MyString(String i) {
this.i = i;
}
@Override
public int hashCode() {
return 100727;
}
}
Según un comentario posterior, Oscar parece revertir lo que dijo antes y reconoce la importancia de los iguales. Sin embargo, todavía parece que la noción de que es igual es lo que importa, no la "misma clase", no está clara (énfasis mío):
"En realidad no. La lista se crea solo si el hash es el mismo, pero la clave es diferente. Por ejemplo, si un String da el código hash 2345 y Integer da el mismo código hash 2345, entonces el número entero se inserta en la lista como String. equals (Integer) es falso. Pero si tienes la misma clase (o al menos .equals devuelve verdadero), entonces se usa la misma entrada. Por ejemplo, new String ("uno") y `new String (" uno ") usado como claves, utilizará la misma entrada. En realidad, este es el punto COMPLETO de HashMap en primer lugar. Compruébelo usted mismo: pastebin.com/f20af40b9 - Oscar Reyes "
versus comentarios anteriores que abordan explícitamente la importancia de una clase idéntica y el mismo código hash, sin mencionar los iguales:
"@delfuego: Compruébalo tú mismo: pastebin.com/f20af40b9 Entonces, en esta pregunta se está usando la misma clase (espera un minuto, se está usando la misma clase ¿verdad?) Lo que implica que cuando se usa el mismo hash se usa la misma entrada se utiliza y no hay "lista" de entradas. - Oscar Reyes "
o
"En realidad, esto aumentaría el rendimiento. Cuantas más colisiones eq menos entradas en la tabla hash, menos trabajo por hacer. ¿No es el hash (que se ve bien) ni la tabla hash (que funciona muy bien)? Apuesto a que está en el objeto creación donde la actuación es degradante. - Oscar Reyes "
o
"@kdgregory: Sí, pero solo si la colisión ocurre con diferentes clases, para la misma clase (que es el caso) se usa la misma entrada. - Oscar Reyes"
Una vez más, puedo malinterpretar lo que Oscar realmente estaba tratando de decir. Sin embargo, sus comentarios originales han causado tanta confusión que parece prudente aclarar todo con algunas pruebas explícitas para que no queden dudas.
[1] - De Effective Java, segunda edición de Joshua Bloch:
Siempre que se invoca en el mismo objeto más de una vez durante la ejecución de una aplicación, el método hashCode debe devolver constantemente el mismo número entero, siempre que no se modifique la información utilizada en comparaciones iguales en el objeto. Este número entero no necesita permanecer consistente de una ejecución de una aplicación a otra ejecución de la misma aplicación.
Si dos objetos son iguales según el método igual s (Obj ect), entonces llamar al método hashCode en cada uno de los dos objetos debe producir el mismo resultado entero.
No es necesario que si dos objetos no son iguales según el método igual s (Object), entonces llamar al método hashCode en cada uno de los dos objetos debe producir resultados enteros distintos. Sin embargo, el programador debe ser consciente de que producir resultados enteros distintos para objetos desiguales puede mejorar el rendimiento de las tablas hash.