Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Changing values in HashSet

Tags:

java

hashset

I've read this question: Changing the elements in a set changes the 'equals' semantics

However, I don't know how to solve the problem that I can't change an item in the HashSet and remove it later.

I have some example sourcecode:

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

Where TestClass is just a POJO that holds a variable (plus getter & setter) and has hashcode() and equals() implemented.

There was a request to show the equals() and hashcode()-methods. These are autogenerated by 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;
}

The result is the following:

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):

When the hashcode has changed, I can't remove the value from the HashSet. As in the linked question, I understand why it is like that, but I don't know how to delete a changed value. Is there any possibility to do so?

like image 700
looper Avatar asked Jun 20 '13 09:06

looper


2 Answers

You are facing the problem because the keys in your hashset are not immutable. If you don't have immutable keys, you will lose the reference of the original key object once modified. And will never be able to get handle of that, which is sometimes referred as memory leaks in collection. So if you use immutable keys, you wouldn't run into this situation.

like image 69
Juned Ahsan Avatar answered Sep 21 '22 00:09

Juned Ahsan


As the question you linked to details, and as others have pointed out, you're encountering the mutable key issue. I'll requote from the Javadoc:

Note: Great care must be exercised if mutable objects are used as set elements. The behavior of a set is not specified if the value of an object is changed in a manner that affects equals comparisons while the object is an element in the set.

As you pointed out, you get that. The question is, how do you actually remove the object given that's the case? You can't use Set.remove(), because your object is lost in the hash table. You can, however, use an Iterator to do it. Something like the following:

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

This approach relies on the fact that standard equals() methods, like you're using, have an instance check, and that check will return true.

Please keep in mind this is not the right way to solve this problem. The right way is to either use an immutable key or "exercise great care", but it is a way to remove a mutated object from a HashSet.

like image 23
sharakan Avatar answered Sep 20 '22 00:09

sharakan