问题的根本原因是 Java 语言规范指定的数组对象的 equals(Object) 和 hashCode() 的语义,以及 java.lang.Object 的 javadocs。
基本上:
- 数组类型的
equals(Object) 方法指定与== 运算符具有相同的语义。
- 指定数组类型的
hashCode() 以返回对象的标识哈希码值。
这意味着根据equals 对象,两个不同的数组永远不会相等。这也意味着分配一个数组的元素永远不会使数组等于另一个。
HashSet 的语义是根据equals(Object) 定义的。这意味着您的问题的答案:
HashSet对数组内容不敏感?
...是正确的:HashSet 对数组的内容不敏感。
你的例子
现在让我们看看你的例子:
HashSet<int[][]> x = new HashSet<int[][]>();
int a[][] = {{1, 2}, {3, 4}};
x.add(a);
a[0][0] = 2;
a[0][1] = 1;
System.out.println(x.contains(a));
这将返回 true,因为您放入 HashSet 的数组与您测试的数组相同。如上所述,数组的内容是无关紧要的。 HashSet 依赖于equals(Object) 方法,用于测试对象身份的数组类型;即如果数组对象是同一个对象。
“合同”...
但假设你这样做了:
HashSet<List<Integer>> x = new HashSet<List<Integer>>();
List<Integer> a = new ArrayList<>();
a.append(1);
a.append(2);
x.add(a);
a.set(0, 3);
System.out.println(x.contains(a));
现在会发生什么?
答案:坏事!
问题是equals(Object) 和hashCode() 的ArrayList 对数组的内容很敏感。但是我们在这里所做的是“违反合同”,即你应该如何处理HashSet 中的对象。您不应该修改作为散列集成员的对象,使其hashCode 值发生变化。
如果您在对象位于HashSet(或者是HashMap 或Hashtable 的键)中时违反了equals / hashcode 的约定,那么该对象很容易在数据结构中丢失。
作为一般规则,使用可变对象作为哈希键是个坏主意。
这是各种 cmets 提出的观点。这是非常重要的一点......虽然它实际上并不是你的例子的根本问题,就像你写的那样。
修正你的例子
那么我们如何才能使您的示例有效;即做(我认为)你真正想做的事情?
这是一个一维数组的简化版本:
public List<Integer> makeSealedList(Integer ... values) {
return Collections.immutableList(Arrays.asList(values.clone()));
}
HashSet<List<Integer>> x = new HashSet<List<Integer>>();
List<Integer> a = makeSealedList(1, 2);
List<Integer> b = makeSealedList(1, 2);
List<Integer> c = makeSealedList(3, 2);
x.add(a);
System.out.println(x.contains(a)); // prints true
System.out.println(x.contains(b)); // prints true
System.out.println(x.contains(c)); // prints false
但请注意,这只适用于“常量数组”,我特意将它们封装以确保我们的列表是常量。
如果您希望能够在哈希集中更改数组并让哈希集自动注意到更改并根据其新的数组重新哈希数组...这是任何 Java 都不支持的SE 集合类型。而且我认为如果没有一大堆额外的基础设施,它是无法实现的。
(对此的实际解决方案是从集合中删除“数组”,对其进行更新,然后重新添加。)