如何使 HashMap 以数组为键工作?
Posted
技术标签:
【中文标题】如何使 HashMap 以数组为键工作?【英文标题】:How to make HashMap work with Arrays as key? 【发布时间】:2013-03-12 15:27:34 【问题描述】:我使用布尔数组作为 HashMap 的键。但是问题是当不同的数组作为键传递时,HashMap 无法获取键,尽管元素是相同的。 (因为它们是不同的对象)。
我怎样才能使它使用数组作为键? 这是代码:
public class main
public static HashMap<boolean[], Integer> h;
public static void main(String[] args)
boolean[] a = false, false;
h = new HashMap<boolean[], Integer>();
h.put(a, 1);
if(h.containsKey(a)) System.out.println("Found a");
boolean[] t = false, false;
if(h.containsKey(t)) System.out.println("Found t");
else System.out.println("Couldn't find t");
a
和 t
两个数组都包含相同的元素,但 HashMap 不为 t
返回任何内容。
如何让它发挥作用?
【问题讨论】:
你不需要,除非你的数组对象是一个自定义的子类,满足计算哈希值和equals的要求。 Can a java array be used as a HashMap key 的可能重复项 【参考方案1】:问题
正如其他人所说,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。
【讨论】:
【参考方案2】:Map
的实现依赖于 key 的 equals
和 hashCode
方法。 java中的数组直接从Object
扩展而来,它们使用默认的equals
和Object
的hashCode
,只比较identity
。
如果我是你,我会创建一个类Key
class Key
private final boolean flag1;
private final boolean flag2;
public Key(boolean flag1, boolean flag2)
this.flag1 = flag1;
this.flag2 = flag2;
@Override
public boolean equals(Object object)
if (!(object instanceof Key))
return false;
Key otherKey = (Key) object;
return this.flag1 == otherKey.flag1 && this.flag2 == otherKey.flag2;
@Override
public int hashCode()
int result = 17; // any prime number
result = 31 * result + Boolean.valueOf(this.flag1).hashCode();
result = 31 * result + Boolean.valueOf(this.flag2).hashCode();
return result;
之后,您可以将您的密钥与Map
一起使用:
Map<Key, Integer> map = new HashMap<>();
Key firstKey = new Key(false, false);
map.put(firstKey, 1);
Key secondKey = new Key(false, false) // same key, different instance
int result = map.get(secondKey); // --> result will be 1
参考: Java hash code from one field
【讨论】:
【参考方案3】:您可以使用接受外部散列和比较策略的库 (trove)。
class MyHashingStrategy implements HashingStrategy<boolean[]>
@Override
public int computeHashCode(boolean[] pTableau)
return Arrays.hashCode(pTableau);
@Override
public boolean equals(boolean[] o1, boolean[] o2)
return Arrays.equals(o1, o2);
Map<boolean[], T> map = new TCustomHashMap<boolean[],T>(new MyHashingStrategy());
【讨论】:
【参考方案4】:Map 使用equals()
来测试你的键是否相同。
Object
中该方法的默认实现测试==
,即引用相等。所以,由于你的两个数组不是同一个数组,equals
总是返回 false。
您需要对两个数组进行映射调用Arrays.equals
以检查是否相等。
您可以创建一个使用Arrays.equals
的数组包装类,然后这将按预期工作:
public static final class ArrayHolder<T>
private final T[] t;
public ArrayHolder(T[] t)
this.t = t;
@Override
public int hashCode()
int hash = 7;
hash = 23 * hash + Arrays.hashCode(this.t);
return hash;
@Override
public boolean equals(Object obj)
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
final ArrayHolder<T> other = (ArrayHolder<T>) obj;
if (!Arrays.equals(this.t, other.t))
return false;
return true;
public static void main(String[] args)
final Map<ArrayHolder<Boolean>, Integer> myMap = new HashMap<>();
myMap.put(new ArrayHolder<>(new Boolean[]true, true), 7);
System.out.println(myMap.get(new ArrayHolder<>(new Boolean[]true, true)));
【讨论】:
我认为在这里使用==
可能会导致误导,因为在Java 中==
运算符用于比较身份。 Map
实际上是使用equals
来比较key。【参考方案5】:
您可以创建一个包含该数组的类。根据值实现该类的 hashCode() 和 equals() 方法:
public class boolarray
boolean array[];
public boolarray( boolean b[] )
array = b;
public int hashCode()
int hash = 0;
for (int i = 0; i < array.length; i++)
if (array[i])
hash += Math.pow(2, i);
return hash;
public boolean equals( Object b )
if (!(b instanceof boolarray))
return false;
if ( array.length != ((boolarray)b).array.length )
return false;
for (int i = 0; i < array.length; i++ )
if (array[i] != ((boolarray)b).array[i])
return false;
return true;
然后您可以使用:
boolarray a = new boolarray( new boolean[] true, true );
boolarray b = new boolarray( new boolean[] true, true );
HashMap<boolarray, Integer> map = new HashMap<boolarray, Integer>();
map.put(a, 2);
int c = map.get(b);
System.out.println(c);
【讨论】:
【参考方案6】:不可能对数组执行此操作,因为任何两个不同的数组都不会比较 equals
,即使它们具有相同的元素。
您需要从容器类进行映射,例如ArrayList<Boolean>
(或简单的List<Boolean>
。也许BitSet
会更合适。
【讨论】:
【参考方案7】:你不能这样做。 t
和 a
将具有不同的 hashCode()
值,因为 java.lang.Array.hashCode()
方法继承自 Object
,它使用引用来计算哈希码(默认实现)。因此,数组的哈希码是依赖于引用的,这意味着您将获得t
和a
的不同哈希码值。此外,equals
不适用于这两个数组,因为这也是基于引用。
您可以做到这一点的唯一方法是创建一个将boolean
数组保留为内部成员的自定义类。然后,您需要覆盖 equals
和 hashCode
,以确保包含具有相同值的数组的实例相等且具有相同的哈希码。
一个更简单的选择可能是使用List<Boolean>
作为键。根据documentation,hashCode()
的List
实现定义为:
int hashCode = 1;
Iterator<E> i = list.iterator();
while (i.hasNext())
E obj = i.next();
hashCode = 31*hashCode + (obj==null ? 0 : obj.hashCode());
如您所见,它取决于列表中的值而不是引用,因此这应该适合您。
【讨论】:
@gaganbm 确保Map<K, V>
的通用键参数是List<Boolean>
而不是ArrayList<Boolean>
。最好使用接口而不是具体类型,以便以后可以根据需要切换实现。所以你的Map
看起来像Map<List<Boolean>, Integer>
。
嗯,它说: List 类型不是通用的;它不能用参数 java.util.List
吗?
@zch 哦,好的。 Eclipse 为我填写了java.awt.List
!【参考方案8】:
boolean[] t;
t = a;
如果你给出这个,而不是boolean[] t = false, false;
,那么你会得到想要的输出。
这是因为Map
将reference
存储为key
,在您的情况下,尽管t
具有相同的值,但它与a
的引用不同。
因此,当您提供t=a
时,它会起作用。
和这个非常相似:-
String a = "ab";
String b = new String("ab");
System.out.println(a==b); // This will give false.
a
和 b
的值相同,但引用不同。因此,当您尝试使用==
比较参考时,它会给出false
。
但如果你给a = b;
,然后尝试比较reference
,你会得到true
。
【讨论】:
这不是我想要问的。假设我在运行时从某个地方获取了一个布尔数组,我将如何检查它是否存在于 Map 中? 您需要获取该元素及其引用,然后使用您刚刚获取的引用检查其值。您不能直接将新数组作为键,因为它的引用不在地图中。【参考方案9】:可能是因为数组返回的 equals() 方法的行为与您预期的不同。您应该考虑实现自己的收集并覆盖 equals() 和 hashCode()。
【讨论】:
以上是关于如何使 HashMap 以数组为键工作?的主要内容,如果未能解决你的问题,请参考以下文章
Java:在 HashMap 中使用 ArrayList,字符串为键