Почему я получаю дубликаты ключей в Java HashMap?
Кажется, я получаю дубликаты ключей в стандартном Java HashMap. Под "duplicate" я подразумеваю, что ключи равны их методу equals()
. Вот проблематичный код:
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));
}
}
И вот результат:
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
Как вы можете видеть, метод equals()
для двух объектов User
возвращает true
, а их хэш-коды одинаковы, но каждый из них образует отдельный key
в map
. Кроме того, map
продолжает различать две клавиши User
в последних четырех вызовах get()
.
Это прямо противоречит документации:
Более формально, если эта карта содержит отображение из ключа k в значение v, такое что (ключ == null? k == null: key.equals(k)), то этот метод возвращает v; в противном случае он возвращает null. (Это может быть не более одного такого отображения.)
Это ошибка? Я что-то упустил? Я запускаю Java версию 1.8.0_92, которую я установил через Homebrew.
EDIT: этот вопрос был отмечен как дубликат этого другого вопроса, но я оставлю этот вопрос так же, как и потому, что он идентифицирует кажущуюся несогласованность с equals()
, тогда как другой вопрос предполагает, что ошибка лежит в hashCode()
. Надеемся, что наличие этого вопроса сделает эту проблему более удобной для поиска.
Ответы
Ответ 1
Проблема заключается в вашем методе equals()
. Подпись Object.equals()
равна equals(OBJECT)
, но в вашем случае это equals(USER)
, поэтому это два совершенно разных метода, и хешмап вызывает тот, у которого есть параметр Object
. Вы можете проверить, что, помещая аннотацию @Override
над вашими равными - это приведет к ошибке компилятора.
Метод equals должен быть:
@Override
public boolean equals(Object other) {
if(other instanceof User){
User user = (User) other;
return userId.equals(user.userId);
}
return false;
}
Как наилучшая практика, вы всегда должны ставить @Override
на методы, которые вы переопределяете - это может сэкономить вам много неприятностей.
Ответ 2
Ваш метод equals не переопределяет equals
, а типы в Map
стираются во время выполнения, поэтому метод фактического равенства равен equals(Object)
. Ваши равные должны выглядеть примерно так:
@Override
public boolean equals(Object other) {
if (!(other instanceof User))
return false;
User u = (User)other;
return userId.equals(u.userId);
}
Ответ 3
ОК, поэтому, прежде всего, код не компилируется. Отсутствует этот метод:
other.getUserId()
Но помимо этого вам понадобится метод @Override equals
, IDE, например Eclipse, также может помочь генерировать equals
и 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;
}
Ответ 4
Как и другие, у вас возникла проблема с сигнатурой метода equals
. В соответствии с наилучшей практикой Java, вы должны реализовать равные значения:
@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);
}
То же самое относится к методу hashCode()
. см. Переопределение метода equals() и hashCode() в Java
Вторая проблема
у вас больше нет дубликатов, но у вас есть новая проблема, ваш HashMap
содержит только один элемент:
map: {Arvo-Part=2}
Это связано с тем, что оба объекта User
ссылаются на одну и ту же строку (JVM String Interning), а с точки зрения HashMap
ваши два объекта одинаковы, поскольку оба объекта эквивалентны в методах hashcode и equals. поэтому, когда вы добавляете второй объект в HashMap
, вы переопределяете свой первый.
чтобы избежать этой проблемы, убедитесь, что вы используете уникальный идентификатор для каждого пользователя
Простая демонстрация ваших пользователей:
![введите описание изображения здесь]()
Ответ 5
Как предложил Хриллис, добавив @Override
к hashCode
и equals
, вы получите ошибку компиляции, потому что подпись метода equals
равна public boolean equals(Object other)
, поэтому вы фактически не переопределяете значение по умолчанию (из класса Object) равен методу. Это приводит к тому, что оба пользователя попадают в один и тот же ковш внутри hashMap
(hashCode переопределяется, и оба пользователя имеют одинаковый хеш-код), но при проверке на равенство они различаются, поскольку используется метод по умолчанию по умолчанию, что означает что адреса памяти сравниваются.
Для получения ожидаемого результата замените метод equals
следующим образом:
@Override
public boolean equals(Object other) {
return getUserId().equals(((User)other).getUserId());
}
Ответ 6
Ключи вместе со своими ассоциативными значениями хранятся в связанном списке node в ковше, и ключи по существу сравниваются в hashmap, используя метод equals(), а не по hashcode.
Если arvo1.equals(arvo2) возвращает true arvo2, будет возвращено значение arvo1 и arvo2 (2), если arvo1.equals(arvo2) возвращает false, другой node будет создан в списке ведра, поэтому, когда вы вызываете get (arvo2 ) вы получите 2, так как arvo1.equals(arvo2) является ложным.