Странное поведение Java HashMap - не удается найти соответствующий объект - PullRequest
3 голосов
/ 29 июля 2010

Я столкнулся с каким-то странным поведением при попытке найти ключ внутри java.util.HashMap, и мне кажется, что я что-то упустил. Сегмент кода в основном:

HashMap<Key, Value> data = ...
Key k1 = ...

Value v = data.get(k1);
boolean bool1 = data.containsKey(k1);
for (Key k2 : data.keySet()) {
    boolean bool2 = k1.equals(k2);
    boolean bool3 = k2.equals(k1);
    boolean bool4 = k1.hashCode() == k2.hashCode();
    break;
}

Это странное для цикла есть, потому что для конкретного выполнения Я знаю, что data содержит только один элемент на данный момент, и это k1, и действительно bool2, bool3 и bool4 будут оцениваться до true в этом исполнении. bool1, однако, будет оценено как false, а v будет нулевым.

Теперь, это часть более крупной программы - я не смог воспроизвести ошибку на меньшей выборке - но все же мне кажется, что независимо от того, что делает остальная часть программы, такое поведение никогда не должно происходить.

РЕДАКТИРОВАТЬ: Я вручную проверил, что хэш-код не изменяется между временем, когда объект был добавлен на карту, и временем, когда он запрашивался. Я буду проверять это место, но есть ли другой вариант?

Ответы [ 6 ]

9 голосов
/ 29 июля 2010

Это может произойти, если хэш-код ключа был изменен после того, как он был вставлен в карту.

Вот пример с поведением, которое вы описали:

public class Key
{
int hashCode = 0;

@Override
public int hashCode() {
    return hashCode;
}

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

public static void main(String[] args) throws Exception {
    HashMap<Key, Integer> data = new HashMap<Key, Integer>();
    Key k1 = new Key();
    data.put(k1, 1);

    k1.hashCode = 1;

    boolean bool1 = data.containsKey(k1);
    for (Key k2 : data.keySet()) {
        boolean bool2 = k1.equals(k2);
        boolean bool3 = k2.equals(k1);
        boolean bool4 = k1.hashCode() == k2.hashCode();

        System.out.println("bool1: " + bool1);
        System.out.println("bool2: " + bool2);
        System.out.println("bool3: " + bool3);
        System.out.println("bool4: " + bool4);

        break;
    }
}
}
3 голосов
/ 29 июля 2010

Из описания API интерфейса Map:

Примечание: следует соблюдать большую осторожность, если изменяемые объекты используются в качестве ключей карты. Поведение карты не указано если значение объекта изменилось таким образом, что влияет на равных сравнения в то время как объект является ключевым на карте. Особый случай этого Запрет состоит в том, что это не допустимо для карты, чтобы содержать сам как ключ. Пока это допустимо для карты, чтобы содержать само по себе как ценность, крайняя осторожность посоветовал: равно и hashCode методы больше не определены такая карта.

Кроме того, существуют очень специфические требования к поведению equals () и hashCode () для типов, используемых в качестве ключей карты. Несоблюдение правил приведёт к неопределённому поведению.

1 голос
/ 29 июля 2010

Если вы уверены, что хэш-код не изменяется между временем вставки ключа и временем проверки содержимого, то где-то что-то серьезно не так.Вы уверены, что используете java.util.HashMap, а не какой-то подкласс?Знаете ли вы, какую реализацию JVM вы используете?

Вот исходный код java.util.HashMap.getEntry(Object key) из Sun 1.6.0_20 JVM:

final Entry<K,V> getEntry(Object key) {
    int hash = (key == null) ? 0 : hash(key.hashCode());
    for (Entry<K,V> e = table[indexFor(hash, table.length)];
         e != null;
         e = e.next) {
        Object k;
        if (e.hash == hash &&
            ((k = e.key) == key || (key != null && key.equals(k))))
            return e;
    }
    return null;

Как видите, он получаетhashCode, идет к соответствующему слоту в таблице, затем выполняет equals проверку каждого элемента в этом слоте.Если это код, который вы запускаете, и хэш-код ключа не изменился, то он должен выполнить проверку на равенство, которая должна завершиться с ошибкой.

Следующим шагом будет предоставление нам некоторыхбольше кода или контекста - минимум методы hashCode и equals вашего класса Key.

В качестве альтернативы, я бы порекомендовал подключиться к отладчику, если вы можете.Посмотрите, в какую корзину хешируется ваш ключ, и просмотрите проверку containsKey, чтобы увидеть, где он не работает.

0 голосов
/ 29 июля 2010

Возможно, класс Key выглядит как

Key
{
    boolean equals = false ;

    public boolean equals ( Object oth )
    {
         try
         {
              return ( equals ) ;
         }
         finally
         {
              equals = true ;
         }
    }
}
0 голосов
/ 29 июля 2010

Если equals () возвращает true для двух объектов, то hashCode () должен возвращать одно и то же значение. Если equals () возвращает false, то hashCode () должен возвращать разные значения. Для справки:

http://www.ibm.com/developerworks/java/library/j-jtp05273.html

0 голосов
/ 29 июля 2010

Является ли это приложение многопоточным? Если это так, другой поток может изменить данные между вызовом data.containsKey(k1) и вызовом data.keySet().

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...