我做的很简单:我想创建HashMap<Pair, ArrayList<Integer>>
,其中Pair
为关键字,ArrayList<Integer>
为值。 Pair
是自定义类,包含元素l
(左)和r
(右)。
首先,我按照以下方式执行此操作:
Map<Pair, ArrayList<Integer>> hashmap = new HashMap<>();
ArrayList<String> stringList = new ArrayList<>();
stringList.add("a");
stringList.add("b");
stringList.add("c");
stringList.add("a");
Pair<String, Integer> aPair = new Pair<>(" ", 1); // HERE will be changed!
for (String aString: stringList) {
aPair.setLeft(aString);
if (!hashmap.containsKey(aPair)){
hashmap.put(aPair, new ArrayList<Integer>());
}
hashmap.get(aPair).add(1);
}
for (Map.Entry<Pair, ArrayList<Integer>> entry: hashmap.entrySet()) {
out.println(entry.getKey().getLeft() + " " + entry.getKey().getRight() + " " + entry.getValue());
}
但输出是:
a 1 [1]
a 1 [1]
a 1 [1, 1]
然而,如果我将上述代码更改为以下内容:
Map<Pair, ArrayList<Integer>> hashmap = new HashMap<>();
ArrayList<String> stringList = new ArrayList<>();
stringList.add("a");
stringList.add("b");
stringList.add("c");
stringList.add("a");
for (String aString: stringList) {
Pair<String, Integer> aPair = new Pair<>(aString, 1); // HERE changed!
if (!hashmap.containsKey(aPair)){
hashmap.put(aPair, new ArrayList<Integer>());
}
hashmap.get(aPair).add(1);
}
for (Map.Entry<Pair, ArrayList<Integer>> entry: hashmap.entrySet()) {
out.println(entry.getKey().getLeft() + " " + entry.getKey().getRight() + " " + entry.getValue());
}
所做的更改是将Pair<String, Integer> aPair
的声明放入for循环中。新结果是我想要的结果:
c 1 [1]
b 1 [1]
a 1 [1, 1]
为什么会这样? Here是一个类似的问题。但它仍然不同。
编辑:正如@Eran在下面的评论中所提到的,自定义的Pair
会覆盖方法hashCode()
和equals()
@Override
public int hashCode() { return left.hashCode() ^ right.hashCode(); }
@Override
public boolean equals(Object o) {
if (!(o instanceof Pair)) return false;
Pair<?, ?> pairo = (Pair<?, ?>) o;
return this.left.equals(pairo.getLeft()) &&
this.right.equals(pairo.getRight());
}
答案 0 :(得分:1)
你在危险的水域踩踏,因为你的钥匙是可变的,请阅读这个为什么它不是一个好主意 - Are mutable hashmap keys a dangerous practice?。
嗯,你的例子就说明了为什么它不是一个好主意。在地图中添加1个键实例,然后对其进行修改,从而有效地修改hashmap中的所有键值对。
答案 1 :(得分:1)
您的第一个代码段不起作用,因为您正在改变已作为Pair
中的密钥的同一HashMap
实例。这允许相同的Pair
实例在HashMap
的多个条目中显示为键(因为在您修改hashCode
实例后计算的新Pair
映射到新的HashMap
HashMap
不包含任何条目的存储桶,并有效地破坏HashMap
。
您不应该改变作为HashMap
密钥的实例(除非您在更新之前将其从Map
中删除,并将更新后的版本放入{{1}}更高版本)。
答案 2 :(得分:1)
如果对象的hashCode()值可以根据其状态改变,那么我们 在基于哈希的键中使用此类对象时必须小心 集合,以确保我们不允许他们的状态改变时 它们被用作哈希键。所有基于散列的集合都假定 对象的哈希值在使用时不会改变 集合中的关键。如果密钥的哈希码在其中发生变化 是一个集合,一些不可预测和混乱的后果 可以跟着。这在实践中通常不是问题 - 事实并非如此 通常的做法是使用像List这样的可变对象作为关键字 HashMap中。
从此answer
答案 3 :(得分:0)
在第一个代码段中,您实际上是为每个地图条目使用相同的密钥对象。您只需修改它的左值,但它仍然指向相同的内存地址。映射需要具有唯一键(每个键必须指向不同的内存地址),这就是为什么需要为每个映射条目添加新的对实例。