2013-06-20 10 views
6

Czytałem to pytanie: Changing the elements in a set changes the 'equals' semanticsZmiana wartości w HashSet

Jednak nie wiem jak rozwiązać ten problem, że nie mogę zmienić pozycję w HashSet i usunąć go później.

mam jakiś przykład kodu źródłowego:

public static void main(String[] args) { 
    TestClass testElement = new TestClass("1"); 
    Set<TestClass> set = new HashSet<>(); 
    set.add(testElement); 
    printIt(testElement, set, "First Set"); 
    testElement.setS1("asdf"); 
    printIt(testElement, set, "Set after changing value"); 
    set.remove(testElement); 
    printIt(testElement, set, "Set after trying to remove value"); 
    testElement.setS1("1"); 
    printIt(testElement, set, "Set after changing value back"); 
    set.remove(testElement); 
    printIt(testElement, set, "Set removing value"); 
} 

private static void printIt(TestClass hullo, Set<TestClass> set, String message) { 
    System.out.println(message + " (hashCode is " + hullo.hashCode() + "):"); 
    for (TestClass testClass : set) { 
     System.out.println(" " + testClass.toString()); 
     System.out.println("  HashCode: " + testClass.hashCode()); 
     System.out.println("  Element is equal: " + hullo.equals(testClass)); 
    } 
} 

Gdzie TestClass tylko POJO, która posiada zmienną (plus getter & setter) i ma hashcode() i equals() realizowane.

Pojawiło się żądanie pokazania metod equals() i hashcode(). Są wygenerowany automatycznie przez Eclipse:

@Override 
public int hashCode() { 
    final int prime = 31; 
    int result = 1; 
    result = prime * result + ((s1 == null) ? 0 : s1.hashCode()); 
    return result; 
} 

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

Wynik jest następujący:

First Set (hashCode is 80): 
    TestClass [s1=1] 
     HashCode: 80 
     Element is equal: true 
Set after changing value (hashCode is 3003475): 
    TestClass [s1=asdf] 
     HashCode: 3003475 
     Element is equal: true 
Set after trying to remove value (hashCode is 3003475): 
    TestClass [s1=asdf] 
     HashCode: 3003475 
     Element is equal: true 
Set after changing value back (hashCode is 80): 
    TestClass [s1=1] 
     HashCode: 80 
     Element is equal: true 
Set removing value (hashCode is 80): 

Gdy hashcode uległa zmianie, nie mogę usunąć wartości z HashSet. Podobnie jak w przypadku linked question, rozumiem, że to jest tak, ale nie wiem, jak usunąć zmienioną wartość. Czy jest taka możliwość?

+0

Czy możesz napisać metody hashcode i metody równości? – mabbas

+0

@mabbas edytowane. – looper

Odpowiedz

8

Masz do czynienia z problemem, ponieważ klucze w hashset nie są niezmienne. Jeśli nie masz niezmiennych kluczy, utracisz odniesienie do pierwotnego obiektu klucza po modyfikacji. I nigdy nie będzie w stanie sobie z tym poradzić, co w kolekcjach określa się czasami jako wycieki pamięci. Więc jeśli użyjesz niezmiennych kluczy, nie natkniesz się na tę sytuację.

+0

Interesujące - czy wynik zestawu haszów jest w rzeczywistości opakowaniem mapy haszującej? – robjohncox

+0

@robjohncox To dlatego, że sposób hashowania działa. Hashcode służą do przechowywania i pobierania obiektów. Przypuśćmy, że utworzysz obiekt kluczowy i kiedy umieścisz go w hashmap, zostanie wywołana jego metoda hashcode do wyliczenia hasza i znalezienia zasobnika do zapisania. Gdy spróbujesz go odzyskać, ponownie zostanie wywołany kod hash, aby uzyskać mieszanie/wiadro gdzie przechowywany jest klucz. Jeśli zmienisz obiekt klucza po zapisaniu w zbiorze/mapie, metoda hashcode zwróci inny skrót dla tego obiektu klucza, który nie jest tym samym, który został użyty do przechowywania klucza. –

+1

Bardzo dobrze, ale do wyszukiwania odniesienia w HashSet (hashmap) nie tylko hashcode jest używany, ale także jest równy jest używany –

1

Po dodaniu testElement do HashSet, wybiera pojemnik na podstawie kodu skrótu dla testElement. Po zapytaniu o numer , jeśli zawiera on kod TestElement, oblicza kod skrótu obiektu, którego szuka i wyszukuje tylko w tym segmencie.

Ponieważ Twoja hashCode() bazuje na polu nieostatecznym, kod skrótu może się zmieniać za kulisami HashSet. Tym samym całkowicie unieważnia podstawowe założenie HashSet.

Prawidłowa implementacja dla Testclass może mieć pole s1 jako ostateczne.

2

Jako pytanie, które łączyłeś ze szczegółami i jak wskazali inni, napotykasz na zmienny kluczowy problem. Będę rekwotowaniu z Javadoc:

Uwaga: Wielka należy zachować ostrożność, jeśli Zmienne obiekty są wykorzystywane jako zestaw elementów. Zachowanie zestawu nie jest określone, jeśli wartość obiektu została zmieniona w sposób, który wpływa na porównanie równań, podczas gdy obiekt jest elementem w zestawie.

Jak zauważyłeś, dostałeś to. Pytanie brzmi: jak właściwie usunąć obiekt, o ile tak jest? Nie można użyć wartości Set.remove(), ponieważ obiekt został utracony w tabeli mieszania. Możesz jednak użyć do tego celu Iterator.Coś jak następuje:

TestClass toRemove = <the same instance, but mutated>; 
for (Iterator<TestClass> iter = set.iterator(); iter.hasNext();) { 
    TestClass item = iter.next(); 
    if (toRemove.equals(item)) { 
    iter.remove(); 
    } 
} 

Podejście to opiera się na fakcie, że standardowe equals() metody, jak używasz, mieć kontrolę instancji, i że kontrola zwróci true.

Należy pamiętać, że to nie jest prawo sposób rozwiązania tego problemu. Odpowiednim sposobem jest użycie niezmiennego klucza lub "ćwiczenie z wielką starannością", ale jest to sposób na usunięcie zmutowanego obiektu z HashSet.