6

我读过这个问题:更改集合中的元素会更改“等于”语义

但是,我不知道如何解决我无法更改 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 和 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):

当哈希码发生变化时,我无法从 HashSet 中删除该值。正如在链接的问题中一样,我理解为什么会这样,但我不知道如何删除更改的值。有没有可能这样做?

4

3 回答 3

8

您正面临这个问题,因为您的哈希集中的键不是不可变的。如果您没有不可变的键,一旦修改,您将失去对原始键对象的引用。并且永远无法处理,这有时被称为集合中的内存泄漏。所以如果你使用不可变的键,你就不会遇到这种情况。

于 2013-06-20T09:23:57.670 回答
2

作为您链接到细节的问题,正如其他人指出的那样,您遇到了可变关键问题。我将从Javadoc重新引用:

注意:如果将可变对象用作集合元素,则必须非常小心。如果对象的值以影响等于比较的方式更改,而对象是集合中的一个元素,则不指定集合的​​行为。

正如你所指出的,你明白了。问题是,鉴于这种情况,您如何实际删除对象?您不能使用Set.remove(),因为您的对象在哈希表中丢失了。但是,您可以使用 anIterator来执行此操作。类似于以下内容:

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.

于 2013-06-20T14:04:24.990 回答
1

当您添加testElement到 HashSet 时,它会根据testElement. 当您询问HashSet它是否包含 aTestElement时,它会计算它正在查找的对象的哈希码并仅在该存储桶中搜索。

由于您hashCode()基于非最终字段,因此哈希码可以在 HashSet 的幕后更改。从而完全否定了HashSet的基本假设。

的正确实现Testclasss1字段作为最终字段。

于 2013-06-20T12:31:29.193 回答