2012-04-02 30 views
5

Algunas clases de JVM importantes (tales como String o List implementations) implementar equals devolviendo Σ 31^n * field_n.hashCode() para cada field_n que es relevante para el método equals. Además, este enfoque es recomendado por Joshua Bloch en Java efectivo (elemento 9).estrategias de aplicación hashCode

Sin embargo, otras clases como Map.Entry implementations siguen reglas diferentes. Por ejemplo, la documentación Map.Entry establece que el código hash de un Map.Entry debe ser

(e.getKey()==null ? 0 : e.getKey().hashCode())^
(e.getValue()==null ? 0 : e.getValue().hashCode()) 

Esto a veces puede no ser práctico utilizar en tablas hash, ya que:

  • el código hash de todas las entradas que tienen la misma clave y valor es 0,
  • dos entradas e1 y e2 para que e1.key = e2.value y e1.value = e2.key tengan el mismo código hash.

¿Por qué elegir esta especificación Java aplicación de Map.Entry hashCode en lugar de, por ejemplo, 31 * (e.getKey()==null ? 0 : e.getKey().hashCode()) + (e.getValue()==null ? 0 : e.getValue().hashCode())?

Edición 1:

para ayudar a determinar el problema, aquí es un ejemplo de código útil cuando el resultado tiene un rendimiento muy pobre debido a las colisiones de hash si muchas entradas tienen el mismo valor de clave y.

Este método calcula las frecuencias de las entradas de diferentes mapas (utilizando Multiset de Guava).

public static <K, V> Multiset<Map.Entry<K, V>> computeEntryCounts(
     Iterable<Map<K, V>> maps) { 
    ImmutableMultiset.Builder<Map.Entry<K, V>> result = ImmutableMultiset.builder(); 
    for (Map<K, V> map : maps) { 
     for (Map.Entry<K, V> entry : map.entrySet()) { 
      result.add(entry); 
     } 
    } 
    return result.build(); 
} 
+0

Su implementación no afecta la salida de la clave y el valor siendo ** nulo **. Además, no hay más de ** una ** entrada con una clave en un Mapa en Java, una clave solo ocurre ** una vez ** en cualquier implementación de Mapa. –

+0

Lo sé, estaba asumiendo que las claves de HashMap son instancias de Map.Entry. Esto puede suceder si desea calcular el recuento total de cada entrada de clave-valor en varios mapas. – jpountz

+0

No puedo ver cómo va a afectar esto a este caso, a menos que desee colocar todo Map.Entry de todos los mapas dentro de un solo mapa para contarlos, pero esto sería claramente incorrecto. –

Respuesta

4

Dudo que haya una buena razón — Creo que es un descuido — pero no es un problema grave. Solo aparecería si tuviera un HashSet<Map.Entry<T,T>> o un HashMap<Map.Entry<T,T>,V>, que no se hace comúnmente. (editar para agregar: O, como señala a continuación Joachim Sauer, un HashSet<Map<T,T>> o una HashMap<Map<T,T>,V> — tampoco comúnmente se hace.)

Tenga en cuenta que HashMap<K,V> hace no uso Map.Entry<K,V>.hashCode(), ya que busca entradas por parte de sus claves solo, por lo que solo usa K.hashCode().

+1

Dado que 'Map.hashCode()' se define en términos del 'hashCode' de sus elementos' Map.Entry', esto también aparece en un 'Map , V2>'. O un 'Set >'. Pero también se pueden evitar fácilmente (y generalmente se deben evitar por motivos de legibilidad y diseño también). –

+0

@JoachimSauer: Buen punto, gracias! – ruakh

+0

Todo lo que necesita para resolver este problema es 'someHashSet.addAll (someMap.entrySet())' o algo así [problema de Guava] (http://code.google.com/p/guava-libraries/issues/detail ? id = 458). Se garantiza que todas las entradas como "a" => "a" colisionarán y se garantiza que el rendimiento será terrible (o al menos menos óptimo desde JDK 8). Raramente te topas con eso, pero cuando lo haces es bastante malo. – maaartinus

0

Mi conjetura personal es que hashCode también debe ser rápido.

Dado que está permitido anular hashCode, no hay ningún problema. Cambiarlo cuando se conoce un algoritmo más apropiado para su caso

+2

Lo sentimos, pero esto no es correcto. 'Map.Entry' es una * interfaz *. No está describiendo un algoritmo * predeterminado * que * it * implementa, pero que las subclases pueden anular; más bien, prescribe un algoritmo específico que los implementadores deben implementar. – ruakh

+0

Cuando sun/oracle necesita en todos los casos una implementación determinada, DEBE usar una clase abstracta en lugar de una interfaz. –