修改包含对象的字段时,HashSet 变得不可靠。 Why/When 或者我应该如何使用 HashSet?

HashSet turns unreliable when modifying a field of a contained object. Why/When or how should I use a HashSet?

当我编辑包含在 HashSet 中的对象时,对象的散列会发生变化,但 HashSet 不会在内部更新。因此,我几乎可以添加相同的对象两次:

TestObject testObject = new TestObject(1, "hello");
Set<TestObject> set = new HashSet<>();
set.add(testObject);
testObject.number = 2;
set.add(testObject);
set.forEach(System.out::println);
//will print
//{number:2, string:hello}
//{number:2, string:hello}

完整的工作代码示例:

import java.util.*;

public class Main {

  public static void main(String[] args) {
    TestObject testObject = new TestObject(1, "hello");
    Set<TestObject> set = new HashSet<>();

     // add initial object
    set.add(testObject);

    // modify object
    testObject.number = 2;
    testObject.string = "Bye";

    // re-add same object
    set.add(testObject);
    set.forEach(System.out::println);
  }
}

class TestObject {

  public int number;
  public String string;

  public TestObject(int number, String string) {
    this.number = number;
    this.string = string;
  }

  @Override
  public int hashCode() {
    return Objects.hash(number, string);
  }

  @Override
  public boolean equals(Object obj) {
    if (!(obj instanceof TestObject)) {
      return false;
    }
    TestObject o = (TestObject) obj;
    return number == o.number && string.equals(o.string);
  }

  @Override
  public String toString() {
    return "{number:" + number + ", string:" + string + "}";
  }
}

这意味着,在修改已包含在 HashSet, theHashSet` 中的对象后,该对象变得不可靠或无效。

修改 Set 中某处包含的对象(可能甚至不知道)对我来说似乎是一个常规用例。还有一些我可能已经做过很多的事情。

这让我回过神来,并向我提出了一个基本问题:如果 HashSet 有这样的行为,我应该在什么时候或为什么要使用它?

你永远不应该用 == 来比较字符串,而是使用 .equals

如您所说,添加一个已经存在的元素不会覆盖 HashSet 中已经存在的元素。在调用 add() 之前使用 remove(),以确保有效插入新值。

旁注:正如一些用户指出的那样,请注意测试中字符串的比较。

好吧,如果你看一下 HashSet 源代码,你会发现它基本上是一个 HashMap<E, Object>,其中的元素是键 - 修改哈希图的键从来都不是一件好事主意。 map/set 不会 如果散列发生变化,实际上 map/set 甚至不知道该变化。

一般来说,HashMap 的键或 HashSet 中的元素应该是不可变的,因为它们的哈希值和相等性不会改变。在大多数情况下,散列和相等性基于这些对象的(业务)身份,因此如果 numberstring 都是该对象身份的一部分,那么您将无法更改它们。

Modifying an object that is somewhere contained in a Set (probably even without knowing) seems a regular use case to me . And something which I probably already have done a lot.

集合中包含的对象经常被修改可能是真的,但这通常意味着未用于生成哈希码或检查相等性的数据被修改。举个例子,假设一个人的哈希码是基于他们的身份证号码。这意味着 hashCode()equals() 应该只基于那个数字,其他一切都可以安全地修改。

所以您可以修改 HashSet 中的元素,只要您不修改它们 "id"。

When or why should I use a HashSet if it has such a behaviour?

如果您需要在 HashSet 中存储可变对象,您有几个选项,这些选项基本上围绕只使用 hashCode()equals() 的不可变部分展开。对于可以通过使用为这些方法提供自定义实现的包装器对象来完成的集合。或者,您可以提取一个或多个不可变属性并将它们用作映射中的键(如果有多个属性,您需要从中构建某种键对象)