问题
正如其他人所说,Java 数组继承.hashcode()
和继承.equals()
自 Object,它使用数组或对象地址的哈希值,完全忽略其内容。解决此问题的唯一方法是将数组包装在一个对象中,该对象根据数组的内容实现这些方法。这就是 Joshua Bloch 写第 25 条:“Prefer lists to arrays”的原因之一。Java 提供了几个执行此操作的类,或者您可以编写自己的使用Arrays.hashCode()
并Arrays.equals()
包含这些方法的正确和有效实现的类。太糟糕了,它们不是默认实现!
只要可行,就对任何基于散列的集合的键使用深度不可修改(或不可变)的类。如果您在将数组(或其他可变对象)存储为哈希表中的键之后对其进行修改,那么它几乎肯定会在该哈希表中的未来.get()
或.contains()
测试中失败。另请参阅可变哈希映射键是一种危险的做法吗?
具体解决方案
// 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()
and (正确且有效),因此each 具有相同的哈希码,并且将等于 each other 。.hashCode()
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 的内置类型推断来处理任何原语或对象(尽管我建议只使用不可变类)。
图书馆解决方案
而不是bList()
使用Google Guava's ImmutableList.of()
或我自己的Paguro's vec()
或其他提供此类预测试方法的库(加上不可变/不可修改的集合等)。
劣质解决方案
这是我在 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 。