在HashSet中改变值

6

我看到了这个问题:更改集合中的元素会更改“equals”语义

然而,我不知道如何解决无法更改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中删除该值。就像相关问题中所述,我明白为什么会这样,但我不知道如何删除已更改的值。是否有可能做到这一点?


请问您能否发布哈希码和相等方法吗? - M. Abbas
3个回答

8

你遇到这个问题是因为你的哈希集合中的键不是不可变的。如果你没有不可变的键,一旦修改后,你将失去原始键对象的引用,并永远无法获取该引用,有时在集合中被称为内存泄漏。因此,如果使用不可变的键,就不会遇到这种情况。


有趣的是,哈希集实际上是哈希映射的包装器吗? - robjohncox
@robjohncox 这是因为哈希的工作方式。哈希码用于存储和检索对象。假设您创建了一个键对象,并将其放入哈希映射中,那么它的哈希码方法将被调用来计算哈希并找到要存储在其中的桶。当您尝试检索它时,又会调用哈希码以获取存储键的哈希/桶。如果您在将键存储在集合/映射中后更改键对象,则哈希码方法将为该键对象返回不同的哈希值,这与用于存储该键的哈希值不同。 - Juned Ahsan
1
非常好,但在HashSet(hashmap)中搜索引用时,不仅使用哈希码,还使用equals方法。 - Volodymyr Levytskyi
@VolodymyrLevytskyi 毫无疑问! - Juned Ahsan
@JunedAhsan 我同意你无法直接索引Set中的对象,就像使用Set方法containsremove一样。你说“永远无法获取该句柄”-但是我们不能通过遍历Set来实现吗?这样我们可以以这种方式获取Set中的所有成员,对吧? - flow2k

2
如您所链接的问题所述,并且正如其他人指出的那样,您遇到了可变键问题。我将从Javadoc中重新引用以下内容:
注意:如果将可变对象用作集合元素,则必须非常小心。如果在对象是集合中的元素时以影响equals比较的方式更改对象的值,则不会指定集合的行为。
正如您指出的那样,您已经了解了这一点。问题是,既然如此,您如何实际删除对象?您不能使用Set.remove(),因为您的对象在哈希表中丢失了。但是,您可以使用迭代器来完成此操作。类似以下内容:
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 中删除已突变对象的一种方法。

1
当您将testElement添加到HashSet中时,它会根据testElement的哈希码选择一个桶。当您询问HashSet是否包含TestElement时,它会计算要查找的对象的哈希码,并仅在那个桶中搜索。
由于您的hashCode()基于非final字段,因此HashSet的背景下哈希码可能会发生变化。因此,完全无效HashSet的基本假设。 Testclass的正确实现应该将s1字段声明为final。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接