问题
正如其他人所说,Java数组从Object继承.hashcode()和.equals(),它使用数组或对象的地址的哈希值,完全忽略了它的内容。解决此问题的唯一方法是将数组包装在一个对象中,该对象根据数组的内容实现这些方法。这就是 Joshua Bloch 写第 25 条:“Prefer lists to arrays”的原因之一。 Java 提供了几个执行此操作的类,或者您可以使用 Arrays.hashCode() 和 Arrays.equals() 编写自己的类,其中包含这些方法的正确和有效的实现。可惜它们不是默认实现!
只要可行,就对任何基于散列的集合的键使用深度不可修改(或不可变)的类。如果您在将数组(或其他可变对象)存储为哈希表中的键之后对其进行修改,那么它几乎肯定会在该哈希表中的未来.get() 或.contains() 测试中失败。另见Are mutable hashmap keys a dangerous practice?
具体解决方案
// Also works with primitive: (boolean... items)
public static List<Boolean> bList(Boolean... items) {
List<Boolean> mutableList = new ArrayList<>();
for (Boolean item : items) {
mutableList.add(item);
}
return Collections.unmodifiableList(mutableList);
}
ArrayList 根据其内容实现.equals() 和.hashCode()(正确且高效),因此每个bList(false, false) 与其他bList(false, false) 具有相同的哈希码,并且将等于其他所有bList(false, false)。
将其包裹在 Collections.unmodifiableList() 中可防止修改。
修改您的示例以使用 bList() 只需要更改一些声明和类型签名。它与您的原件一样清晰且几乎一样简短:
public class main {
public static HashMap<List<Boolean>, Integer> h;
public static void main(String[] args){
List<Boolean> a = bList(false, false);
h = new HashMap<>();
h.put(a, 1);
if(h.containsKey(a)) System.out.println("Found a");
List<Boolean> t = bList(false, false);
if(h.containsKey(t)) System.out.println("Found t");
else System.out.println("Couldn't find t");
}
}
通用解决方案
public <T> List<T> bList(T... items) {
List<T> mutableList = new ArrayList<>();
for (T item : items) {
mutableList.add(item);
}
return Collections.unmodifiableList(mutableList);
}
上述解决方案的其余部分没有改变,但这将利用 Java 的内置类型推断来处理任何原语或对象(尽管我建议只使用不可变类)。
库解决方案
使用 Google Guava's ImmutableList.of() 或我自己的 Paguro's vec() 或其他提供此类预测试方法的库(加上不可变/不可修改的集合等),而不是 bList()。
劣质解决方案
这是我在 2017 年的原始答案。我把它留在这里是因为有人觉得它很有趣,但我认为它是二流的,因为 Java 已经包含 ArrayList 和 Collections.unmodifiableList() 可以解决这个问题。使用 .equals() 和 .hashCode() 方法编写自己的集合包装器比使用内置方法更费力、更容易出错、更难验证,因此更难阅读。
这应该适用于任何类型的数组:
class ArrayHolder<T> {
private final T[] array;
@SafeVarargs
ArrayHolder(T... ts) { array = ts; }
@Override public int hashCode() { return Arrays.hashCode(array); }
@Override public boolean equals(Object other) {
if (array == other) { return true; }
if (! (other instanceof ArrayHolder) ) {
return false;
}
//noinspection unchecked
return Arrays.equals(array, ((ArrayHolder) other).array);
}
}
这是您转换为使用 ArrayHolder 的具体示例:
// boolean[] a = {false, false};
ArrayHolder<Boolean> a = new ArrayHolder<>(false, false);
// h = new HashMap<boolean[], Integer>();
Map<ArrayHolder<Boolean>, Integer> h = new HashMap<>();
h.put(a, 1);
// if(h.containsKey(a)) System.out.println("Found a");
assertTrue(h.containsKey(a));
// boolean[] t = {false, false};
ArrayHolder<Boolean> t = new ArrayHolder<>(false, false);
// if(h.containsKey(t)) System.out.println("Found t");
assertTrue(h.containsKey(t));
assertFalse(h.containsKey(new ArrayHolder<>(true, false)));
我使用过 Java 8,但我认为 Java 7 拥有您所需的一切。我使用TestUtils 测试了 hashCode 和 equals。