2011-07-20 20 views
9

Zastanawiam się, co by się stało, gdyby klucz z HashMap jest zmienny, poniżej program badań pokazują, że i ja jestem w stanie zrozumieć, gdy oba równe i hashCode metody zwraca wartość true i sam, dlaczego hashmap.containsKey return false.Aktualizacja Java HashMap klucz

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

To wyjście:

prawda fałszywe 6 6 prawdą

+0

Ten artykuł na temat podejścia Pythona może być interesujący: http://pyfaq.infogami.com/why-must-dictionary-keys-be-immutable – jtoberon

Odpowiedz

14

javadoc wyjaśnia

Uwaga: wielki należy zachować ostrożność, jeśli Zmienne obiekty są używane jako klucze mapy. Zachowanie mapy nie jest określone, jeśli wartość obiektu jest zmieniana w sposób, który wpływa na porównanie równań, podczas gdy obiekt jest kluczem na mapie.

Zasadniczo nie używać zmienny przedmiotów jak klucze w mapą, masz zamiar dostać spalony

ekstrapolacji, ponieważ dokumentacja może nie pojawić się jasne, uważam, że istotne jest tutaj punkt ` zmienione w sposób, który wpływa na równe "i wydaje się, że zakładasz, że równa się (Obiekt) jest wywoływana za każdym razem, gdy zawiera się wywołanie. Dokumenty tego nie mówią, co sugeruje, że można im pozwolić na obliczenia pamięci podręcznej.

Patrząc na source, wygląda na to, że ponieważ Twój kod hash zwraca inną wartość (5, teraz 6), możliwe jest, że jest on wyszukiwany w innym segmencie na podstawie szczegółów implementacji.

+0

Komentując moją własną odpowiedź: wydaje mi się, że powiązane implementacja nie spełnia obowiązków wynikających z dokumentacji, ponieważ zmienność danego typu wpływa na hashCode, a nie na równe. – ptomli

+0

Tak, ale implementacja hashCode, która jest zgodna z umową określoną przez Object, spowoduje zachowanie równoważne funkcjonalnie. – Affe

+0

Not quite: "Metoda hashCode musi konsekwentnie zwracać tę samą liczbę całkowitą, pod warunkiem, że nie są modyfikowane informacje używane w porównywaniu równań obiektu". Ponieważ dane używane dla równań uległy zmianie, jest również dopuszczalne, aby zmienił się parametr hashCode. Dokumentacja Mapy sugeruje, że równy jest arbitrem równości, podczas gdy implementacja używa błędnie HashCode do znalezienia wpisu, o ile wiem. Jedynym wybawcą jest to, że dokumentacja Mapy nie jest dokładnie jasna, w jaki sposób implementacje mogą wykorzystywać równe wartości w całym okresie ich istnienia. I nie znam źródła pochodzenia połączonego źródła. – ptomli

6

Obiekt HashMap umieszcza obiekt w lokalizacji dla klucza hashowania 5. Następnie zmień klucz na 6 i użyj containsKey, aby zapytać mapę, czy zawiera obiekt. Mapa wygląda na pozycji 6 i nie znajduje nic, więc odpowiada false.

Więc nie rób tego.

+0

To ma dla mnie sens. Poszedłem za tym, co powiedziałeś, a następnie dodałem 'map.put (m1," m1 ");' po linii 'm1.setA (6);'. Rezultatem było "true true 6 6 true". – smslce

+0

Należy usunąć go z mapy mieszania przed zmianą klucza, w przeciwnym razie spowoduje to wyciek pamięci. – starblue

9

Możesz myśleć o tym, czy mapa ma 16 wiader. Kiedy dasz mu obiekt z A == 5, rzuci go do wiadra 5. Teraz możesz zmienić A na 6, ale wciąż jest w wiadrze 5. Mapa nie wie, że zmieniłeś A, to nie zmienia porządku rzeczy wewnętrznie.

Teraz przychodzisz z innym obiektem z A == 6 i pytasz Mapę, czy ma jedną z nich. To idzie i wygląda w wiadrze 6 i mówi: "Nie, nic tam". Nie pójdzie i sprawdzi dla ciebie wszystkie inne wiadra.

Oczywiście, jak rzeczy wkładane do wiader są bardziej skomplikowane, ale tak to działa w rdzeniu.

0

Po umieszczeniu "m1" za pierwszym razem, hashCode() był 5. Tak więc HashMap użył 5, aby umieścić wartość w odpowiednim wiadrze. Po zmianie m2, było to hashCode(), więc gdy próbowaliście szukać wartości, którą włożyliście, to wiadro, na które patrzyło było inne.

+0

, chyba że zmienił się rozmiar mapy między czasem zmutowania a czasem, w którym próbowałeś go ponownie wyszukać. Po zmianie rozmiaru hashCodes są ponownie obliczane. –

0

Przykład kodu dołączony do odpowiedzi .

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

Kompiluje i uruchamia. Rezultat:

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

Używam Java 6 w systemie Linux.