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);
}