问题
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?
回答1:
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.
回答2:
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
.
回答3:
When you add testElement
to the HashSet, it selects a bucket based on the hash code for testElement
. When you ask the HashSet
if it contains a TestElement
, it computes the hash code of the object that it's looking for and searches only in that bucket.
As your hashCode()
is based on non-final field, the hash code can change behind the scenes of the HashSet. Thus completely invalidating the basic assumption of the HashSet.
A correct implementation for the Testclass
would have the s1
field as final.
来源:https://stackoverflow.com/questions/17209907/changing-values-in-hashset