sort mapa java hashmap equals hashcode

mapa - ¿Por qué obtengo claves duplicadas en Java HashMap?



sort mapa java (5)

Esta pregunta ya tiene una respuesta aquí:

Parece que estoy obteniendo claves duplicadas en el Java HashMap estándar. Por "duplicado", me refiero a que las claves son iguales por su método equals() . Aquí está el código problemático:

import java.util.Map; import java.util.HashMap; public class User { private String userId; public User(String userId) { this.userId = userId; } public boolean equals(User other) { return userId.equals(other.getUserId()); } public int hashCode() { return userId.hashCode(); } public String toString() { return userId; } public static void main(String[] args) { User arvo1 = new User("Arvo-Part"); User arvo2 = new User("Arvo-Part"); Map<User,Integer> map = new HashMap<User,Integer>(); map.put(arvo1,1); map.put(arvo2,2); System.out.println("arvo1.equals(arvo2): " + arvo1.equals(arvo2)); System.out.println("map: " + map.toString()); System.out.println("arvo1 hash: " + arvo1.hashCode()); System.out.println("arvo2 hash: " + arvo2.hashCode()); System.out.println("map.get(arvo1): " + map.get(arvo1)); System.out.println("map.get(arvo2): " + map.get(arvo2)); System.out.println("map.get(arvo2): " + map.get(arvo2)); System.out.println("map.get(arvo1): " + map.get(arvo1)); } }

Y aquí está la salida resultante:

arvo1.equals(arvo2): true map: {Arvo-Part=1, Arvo-Part=2} arvo1 hash: 164585782 arvo2 hash: 164585782 map.get(arvo1): 1 map.get(arvo2): 2 map.get(arvo2): 2 map.get(arvo1): 1

Como puede ver, el método equals() en los dos objetos de User se está volviendo true y sus códigos hash son iguales, pero cada uno forma una key distinta en el map . Además, el map continúa distinguiendo entre las dos claves de User en las últimas cuatro llamadas get() .

Esto contradice directamente la documentation :

Más formalmente, si este mapa contiene una asignación de una clave k a un valor v tal que (clave == null? K == null: key.equals (k)), este método devuelve v; De lo contrario, devuelve nulo. (Puede haber como máximo un mapeo de este tipo).

¿Es esto un error? ¿Me estoy perdiendo de algo? Estoy ejecutando la versión 1.8.0_92 de Java, que instalé a través de Homebrew.

EDITAR: Esta pregunta se ha marcado como un duplicado de esta otra pregunta , pero la dejo como tal porque identifica una inconsistencia aparente con equals() , mientras que la otra pregunta asume que el error está en hashCode() . Con suerte, la presencia de esta pregunta hará que este problema sea más fácil de buscar.


Al igual que otros respondieron, tuvo un problema con la firma del método equals . Según Java es igual a la mejor práctica, debería implementar iguales como los siguientes:

@Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; User user = (User) o; return userId.equals(user.userId); }

Lo mismo se aplica para el método hashCode() . vea Cómo anular el método equals () y hashCode () en Java

El segundo problema

Ya no tiene duplicados ahora, pero tiene un problema nuevo, su HashMap contiene solo un elemento:

map: {Arvo-Part=2}

Esto se debe a que ambos objetos de User están haciendo referencia a la misma Cadena ( JVM String Interning ), y desde la perspectiva de HashMap , sus dos objetos son iguales, ya que ambos objetos son equivalentes en los métodos hashcode e iguales. así que cuando agrega su segundo objeto al HashMap usted reemplaza al primero. para evitar este problema, asegúrese de utilizar una ID única para cada usuario

Una simple demostración en sus usuarios:


Como Chrylis sugirió, al agregar @Override a hashCode y equals , obtendrá un error de compilación porque la firma del método equals es public boolean equals(Object other) , por lo que en realidad no está reemplazando el valor predeterminado (de la clase Object) es igual al metodo Esto lleva a la situación de que ambos usuarios terminan en el mismo grupo dentro del hashMap (hashCode está anulado y ambos usuarios tienen el mismo código hash), pero cuando se verifica la igualdad, son diferentes, ya que se utiliza el método equals por defecto, lo que significa que Se comparan las direcciones de memoria.

Reemplace el método de equals con lo siguiente para obtener el resultado esperado:

@Override public boolean equals(Object other) { return getUserId().equals(((User)other).getUserId()); }


El problema radica en su método equals() . La firma de Object.equals() es equals(OBJECT) , pero en su caso es equals(USER) , por lo que estos son dos métodos completamente diferentes y el hashmap llama al uno con el parámetro Object . Puede verificar que al colocar una anotación @Override sobre sus iguales, generará un error de compilación.

El método equals debería ser:

@Override public boolean equals(Object other) { if(other instanceof User){ User user = (User) other; return userId.equals(user.userId); } return false; }

Como práctica recomendada, siempre debe poner a @Override en los métodos que reemplaza, ya que puede ahorrarle muchos problemas.


OK, primero que todo, el código no se compila. Falta este método:

other.getUserId()

Pero aparte de eso, deberá @Override equals método @Override equals , IDE como Eclipse también puede ayudar a generar equals y hashCode btw.

@Override public boolean equals(Object obj) { if(this == obj) return true; if(obj == null) return false; if(getClass() != obj.getClass()) return false; User other = (User) obj; if(userId == null) { if(other.userId != null) return false; } else if(!userId.equals(other.userId)) return false; return true; }


Su método de iguales no anula a los equals , y los tipos en el Map se borran en tiempo de ejecución, por lo que el método real de iguales llamado es equals(Object) . Tus iguales deberían verse más así:

@Override public boolean equals(Object other) { if (!(other instanceof User)) return false; User u = (User)other; return userId.equals(u.userId); }