Почему пользовательские объекты не являются эквивалентными ключами для HashMap? - PullRequest
7 голосов
/ 05 июня 2011

У меня проблемы с использованием моего собственного класса в качестве ключа для HashMap

 public class ActorId {
     private final int playerId;
     private final int id;

     ActorId(int playerId, int id) {
         this.playerId = playerId;
         this.id = id;
     }

     public boolean equals(ActorId other) {
         return this.id == other.id && this.playerId == other.playerId;
     }

     public int hashCode() {
         int hash = 1;
         hash = hash * 31 + playerId;
         hash = hash * 31 + id;
         return hash;
     }

     public String toString() {
         return "#" + playerId + "." + id;
     }

     public int getPlayerId() {
         return playerId;
     }
 }

Вот неудачный тест JUnit

 import static org.junit.Assert.*;
 import java.util.Map;
 import org.junit.Test;

 public class ActorIdTest {
     @Test
     public final void testAsMapKey() {
         ActorId a = new ActorId(123, 345);
         ActorId b = new ActorId(123, 345);

         assertTrue(a.equals(b));
         assertEquals(a.hashCode(), b.hashCode());

         // Works with strings as keys
         Map<String, String> map1 = new java.util.HashMap<String, String>();

         map1.put(a.toString(), "test");
         assertEquals("test", map1.get(a.toString()));
         assertEquals("test", map1.get(b.toString()));
         assertEquals(1, map1.size()); 

         // But not with ActorIds
         Map<ActorId, String> map2 = new java.util.HashMap<ActorId, String>();

         map2.put(a, "test");
         assertEquals("test", map2.get(a));
         assertEquals("test", map2.get(b)); // FAILS here
         assertEquals(1, map2.size()); 

         map2.put(b, "test2");
         assertEquals(1, map2.size());
         assertEquals("test2", map2.get(a));
         assertEquals("test2", map2.get(b));
     }
 }

Ответы [ 4 ]

9 голосов
/ 05 июня 2011

Вам нужно изменить

public boolean equals(ActorId other) {
    ....
}

до

public boolean equals(Object other) {
    ....
}

Совет дня: Всегда используйте @Override аннотацию.

Если бы вы использовали аннотацию @Override, компилятор поймал бы ошибку и сказал бы:

Метод equals (ActorId) типа ActorId должен переопределить или реализовать метод супертипа

3 голосов
/ 05 июня 2011

Ваш код правильный, но вам также необходимо переопределить метод equals, унаследованный от Object.

Добавьте это к вашему ActorId классу:

@Override
 public boolean equals(Object other) {
    if(other == null || other.getClass() != getClass())
        return false;
    return equals((ActorId)other);
 }
1 голос
/ 11 августа 2011

Вы обязательно должны переопределить метод equals (Object), и для определенной реализации Map (HashMap) также необходимо переопределить метод hashCode ().

У меня была такая же проблема, ибез специальной реализации hashCode метод equals класса «ActorId» никогда не вызывался.

0 голосов
/ 01 февраля 2013

По умолчанию Java вызывает логическое значение equals(Object obj); Таким образом, вы входите правильно, но если вы хотите переопределить, equals () использует Object в качестве параметра и проверяет класс по instanceOf или getClass() и выполняет приведение класса.

if (obj instanceOf ActorId) {
    ActorId other = (ActorId)obj;
    ... compare fields
}
...