我读过这个问题:Changing the elements in a set changes the 'equals' semantics
但是,我不知道如何解决我无法更改HashSet中的项目并在以后删除它的问题。
我有一些示例源代码:
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));
}
}
其中TestClass只是一个包含变量的POJO(加上getter&amp; setter)并且实现了hashcode()和equals()。
有一个请求显示equals()和hashcode() - 方法。这些是由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;
}
结果如下:
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):
当哈希码发生变化时,我无法从哈希集中删除该值。与linked question中一样,我理解为什么就是这样,但我不知道如何删除更改后的值。有没有可能这样做?
答案 0 :(得分:8)
您遇到了问题,因为哈希集中的键不是不可变的。如果您没有不可变密钥,则修改后将丢失原始密钥对象的引用。并且永远无法处理它,有时也称为集合中的内存泄漏。因此,如果您使用不可变密钥,则不会遇到这种情况。
答案 1 :(得分:2)
关于细节的问题,正如其他人所指出的那样,你遇到了可变的关键问题。我将从Javadoc:
重新引用注意:如果将可变对象用作set,则必须非常小心 元素。如果a的值,则不指定集合的行为 对象以影响等于比较的方式改变 对象是集合中的元素。
正如你所指出的,你明白了。问题是,在具体情况下,你如何实际删除对象?您无法使用Set.remove()
,因为您的对象在哈希表中丢失了。但是,您可以使用Iterator
来执行此操作。如下所示:
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();
}
}
这种方法依赖于标准equals()
方法(如您正在使用)进行实例检查,并且该检查将返回true。
请记住,这不是解决此问题的正确方法。正确的方法是使用不可变密钥或“非常小心”,但这是一种从HashSet
中删除变异对象的方法。
答案 2 :(得分:1)
当您向HashSet添加testElement
时,它会根据testElement
的哈希码选择一个存储桶。当您询问HashSet
它是否包含TestElement
时,它会计算它正在查找的对象的哈希码,并仅在该桶中搜索。
由于您的hashCode()
基于非final字段,哈希代码可以在HashSet的幕后更改。因此完全使HashSet的基本假设无效。
Testclass
的正确实施会将s1
字段作为最终字段。