java hashmap 似乎允许 2 个重复键
java hashmap seems to allow 2 duplicate keys
执行以下代码:
public class Main {
public static void main(String[] args) {
Util util = new Util();
util.addBlock(1);
util.addBlocks(util.getBlocks());
}
}
public class Util {
public static HashMap<Long, Integer> blockMap = new HashMap<>();
private Gson gson = new Gson();
public void addBlocks(String json){
Map map = gson.fromJson(json, Map.class);
blockMap.putAll(map);
System.out.println(blockMap.keySet());
}
public void addBlock(int i){
blockMap.put(0L, i);
}
public String getBlocks(){
return gson.toJson(blockMap);
}
}
我得到了输出
[0, 0]
来自打印 System.out.println(blockMap.keySet());
.
因此,出于某种原因,我有一个 Map<Long, Integer>
,其中包含两个 Long
,其中值 0
作为键。还有一个键集 Set<Long>
和两个 0
。但是map和set不允许重复键,这怎么可能?
代码首先向地图添加一个简单的条目:
blockMap.put(0L, i);
然后,我通过使用 GSON 将映射转换为 JSON 字符串,然后再转换回映射来添加另一个条目:
gson.toJson(blockMap);
...
Map map = gson.fromJson(json, Map.class);
blockMap.putAll(map);
我希望它会覆盖之前的条目,而不是添加另一个具有相同、重复的键的条目。
说明
您对结果的解释略有错误。你说它打印
[0, 0]
并得出结论,您有两个类型为 Long
且值为 0
的键。但这实际上是错误的。一个确实是Long
,来自这里:
blockMap.put(0L, i);
但另一个是 String
"0"
,在印刷品中看起来一样。
原始类型
您可能会对为什么 HashMap<Long, Block>
中有一个字符串键感到困惑。毕竟,它将密钥指定为 Long
,而不是 String
.
问题在这里:
Map map = gson.fromJson(json, Map.class);
blockMap.putAll(map);
此处发生的情况是您使用 putAll
,但提供 Map
。请注意,它没有泛型,它是 原始类型。
通过使用原始类型,您基本上牺牲了所有类型安全并承诺 Java "Yeah, yeah, trust me, I know what I am doing."。所以 Java 只会相信你实际上给了它一个 Map<Long, Block>
,但你实际上给了它一个 Map<String, String>
。
堆污染
在 Java 中,泛型在运行时被删除。也就是说,通过编译后,Java 对泛型一无所知,基本上可以随意混合类型,或多或少只是“Map<Object, Object>
”。
因此,您设法欺骗了 Java 的安全泛型类型系统,并将 String
注入到只接受 Long
作为键的映射中。这称为堆污染。这就是你永远不应该使用原始类型的主要原因。使用原始类型没有任何好处。
堆污染的一个小例子:
List<Dog> dogs = new ArrayList<>(); // safe collection
dogs.add(new Dog());
// dogs.add(new Cat()); // not allowed, dogs is safe
List raw = dogs; // raw handle to safe collection
raw.add(new Cat()); // works! heap pollution
// compiles, but throws at run-time,
// since it is actually a cat, not a dog
Dog dog = dogs.get(1);
备注
有关此主题的更多信息,请阅读:
- Java generics type erasure: when and what happens?
- Heap pollution
正如@Zabuza 指出的那样,这里的问题是您使用的原始地图没有在运行时删除的泛型。试试这个例子:
for (Object key: Util.blockMap.keySet()) {
System.out.println("key: " + key + " type: " + key.getClass());
}
您会看到第一个键的类型为 Long
,而第二个键的类型为 String
因为 equals 方法在 String
中定义为:
if (anObject instanceof String) {
// ...
}
return false;
等于总是return假
希望下面的代码能解开你的疑惑:
public void addBlocks(String json) {
Map map = gson.fromJson(json, Map.class);
blockMap.putAll(map);
System.out.println(blockMap.keySet());
Iterator itr=blockMap.keySet().iterator();
while(itr.hasNext()){
System.out.println(itr.next().getClass());
}
}
使用此代码输出:
[0, 0]
class java.lang.Long
class java.lang.String
执行以下代码:
public class Main {
public static void main(String[] args) {
Util util = new Util();
util.addBlock(1);
util.addBlocks(util.getBlocks());
}
}
public class Util {
public static HashMap<Long, Integer> blockMap = new HashMap<>();
private Gson gson = new Gson();
public void addBlocks(String json){
Map map = gson.fromJson(json, Map.class);
blockMap.putAll(map);
System.out.println(blockMap.keySet());
}
public void addBlock(int i){
blockMap.put(0L, i);
}
public String getBlocks(){
return gson.toJson(blockMap);
}
}
我得到了输出
[0, 0]
来自打印 System.out.println(blockMap.keySet());
.
因此,出于某种原因,我有一个 Map<Long, Integer>
,其中包含两个 Long
,其中值 0
作为键。还有一个键集 Set<Long>
和两个 0
。但是map和set不允许重复键,这怎么可能?
代码首先向地图添加一个简单的条目:
blockMap.put(0L, i);
然后,我通过使用 GSON 将映射转换为 JSON 字符串,然后再转换回映射来添加另一个条目:
gson.toJson(blockMap);
...
Map map = gson.fromJson(json, Map.class);
blockMap.putAll(map);
我希望它会覆盖之前的条目,而不是添加另一个具有相同、重复的键的条目。
说明
您对结果的解释略有错误。你说它打印
[0, 0]
并得出结论,您有两个类型为 Long
且值为 0
的键。但这实际上是错误的。一个确实是Long
,来自这里:
blockMap.put(0L, i);
但另一个是 String
"0"
,在印刷品中看起来一样。
原始类型
您可能会对为什么 HashMap<Long, Block>
中有一个字符串键感到困惑。毕竟,它将密钥指定为 Long
,而不是 String
.
问题在这里:
Map map = gson.fromJson(json, Map.class);
blockMap.putAll(map);
此处发生的情况是您使用 putAll
,但提供 Map
。请注意,它没有泛型,它是 原始类型。
通过使用原始类型,您基本上牺牲了所有类型安全并承诺 Java "Yeah, yeah, trust me, I know what I am doing."。所以 Java 只会相信你实际上给了它一个 Map<Long, Block>
,但你实际上给了它一个 Map<String, String>
。
堆污染
在 Java 中,泛型在运行时被删除。也就是说,通过编译后,Java 对泛型一无所知,基本上可以随意混合类型,或多或少只是“Map<Object, Object>
”。
因此,您设法欺骗了 Java 的安全泛型类型系统,并将 String
注入到只接受 Long
作为键的映射中。这称为堆污染。这就是你永远不应该使用原始类型的主要原因。使用原始类型没有任何好处。
堆污染的一个小例子:
List<Dog> dogs = new ArrayList<>(); // safe collection
dogs.add(new Dog());
// dogs.add(new Cat()); // not allowed, dogs is safe
List raw = dogs; // raw handle to safe collection
raw.add(new Cat()); // works! heap pollution
// compiles, but throws at run-time,
// since it is actually a cat, not a dog
Dog dog = dogs.get(1);
备注
有关此主题的更多信息,请阅读:
- Java generics type erasure: when and what happens?
- Heap pollution
正如@Zabuza 指出的那样,这里的问题是您使用的原始地图没有在运行时删除的泛型。试试这个例子:
for (Object key: Util.blockMap.keySet()) {
System.out.println("key: " + key + " type: " + key.getClass());
}
您会看到第一个键的类型为 Long
,而第二个键的类型为 String
因为 equals 方法在 String
中定义为:
if (anObject instanceof String) {
// ...
}
return false;
等于总是return假
希望下面的代码能解开你的疑惑:
public void addBlocks(String json) {
Map map = gson.fromJson(json, Map.class);
blockMap.putAll(map);
System.out.println(blockMap.keySet());
Iterator itr=blockMap.keySet().iterator();
while(itr.hasNext()){
System.out.println(itr.next().getClass());
}
}
使用此代码输出:
[0, 0]
class java.lang.Long
class java.lang.String