Обновление ключа Java HashMap - PullRequest
10 голосов
/ 20 июля 2011

Мне просто интересно, что произойдет, если ключ HashMap является изменяемым, тестовая программа ниже демонстрирует это, и я не могу понять, когда возвращаются оба метода equals и hashCode истинное и то же значение, почему hashmap.containsKey возвращает false.

public class MutableKeyHashMap {

    public static void main(String []a){

            HashMap<Mutable, String> map = new HashMap<Mutable, String>();
            Mutable m1 = new Mutable(5);
            map.put(m1, "m1");
            Mutable m2 = new Mutable(5);
            System.out.println(map.containsKey(m2));    

            m2.setA(6);
            m1.setA(6);
            Mutable m3 = map.keySet().iterator().next();

            System.out.println(map.containsKey(m2)+"    "+m3.hashCode()+"       "+m2.hashCode()+"       "+m3.equals(m2));   

    }
}
class Mutable {

    int a;

    public Mutable(int a) {

        this.a = a;
    }

    @Override
    public boolean equals(Object obj) {

        Mutable m = (Mutable) obj;
        return m.a == this.a ? true : false; 
    }

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

    public void setA(int a) {

        this.a = a;
    }

    public int getA() {
        return a;
    }
} 

Это вывод:

верно ложь 6 6 верно

Ответы [ 5 ]

14 голосов
/ 20 июля 2011

Javadoc объясняет это

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

По сути, не используйте изменяемые объекты в качестве ключей на карте, вы будете сожжены

Чтобы экстраполировать, потому что документы могут показаться неясными, я считаю, что уместная точка здесь «изменена таким образом, что влияет на равные», и вы, похоже, предполагаете, что вызывается метод equals (Object) каждый раз, когда вызывается переменная В документах этого не сказано, формулировка подразумевает, что им может быть разрешено кэшировать вычисления.

Глядя на источник , кажется, что, поскольку ваш hashCode возвращает другое значение (было 5, а теперь 6), возможно, что оно ищется в другом сегменте на основе деталей реализации.

10 голосов
/ 20 июля 2011

Вы можете подумать, если таким образом, Карта имеет 16 блоков.Когда вы даете ему объект с A == 5, он выбрасывает его в корзину 5. Теперь вы можете изменить A на 6, но он все еще в корзине 5. Карта не знает, что вы изменили A, она не переставляет вещивнутренне.

Теперь вы подходите с другим объектом с A == 6 и спрашиваете карту, есть ли у него один из них.Он идет и смотрит в ведро 6 и говорит: «Нет, ничего нет».Он не собирается проверять все остальные сегменты для вас.

Очевидно, что сложить вещи в сегменты сложнее, чем это, но вот как это работает в ядре.

6 голосов
/ 20 июля 2011

HashMap помещает ваш объект в местоположение для хэш-ключа 5. Затем вы измените ключ на 6 и с помощью containsKey спросите карту, содержит ли он объект. Карта смотрит на позицию 6 и ничего не находит, поэтому отвечает false.

Так что не делай этого.

0 голосов
/ 25 мая 2012

Пример кода, сопровождающий ответ ptomli .

import java.util.*;

class Elem {
    private int n;

    public Elem(int n) {
        this.n = n;
    }

    public void setN(int n) {
        this.n = n;
    }

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

    @Override
    public boolean equals(Object e) {
        if (this == e)
            return true;
        if (!(e instanceof Elem))
            return false;
        Elem an = (Elem) e;
        return n == an.n;
    }
}

public class MapTest {
    public static void main (String [] args)  {
        Elem e1 = new Elem(1);
        Elem e2 = new Elem(2);

        HashMap map = new HashMap();
        map.put(e1, 100);
        map.put(e2, 200);

        System.out.println("before modification: " + map.get(e1));  
        e1.setN(9);
        System.out.println("after modification using updated key: " + map.get(e1)); 

        Elem e3 = new Elem(1);
        System.out.println("after modification using key which equals to the original key: " + map.get(e3));    
    }
}

Компилирует и запускает его. Результат:

before modification: 100
after modification using updated key: null
after modification using key which equals to the original key: null

Я использую Java 6 в Linux.

0 голосов
/ 20 июля 2011

Когда вы вводите «m1» в первый раз, hashCode() был равен 5. Таким образом, HashMap использовал 5, чтобы поместить значение в соответствующий сегмент. После изменения m2 значение hashCode() равнялось 6, поэтому, когда вы пытались найти введенное вами значение, оно оказалось другим.

...