java hashmap seems to allow 2 duplicate keys

martse :

Executing the following code:

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);
    }
}

I get the output

[0, 0]

from the print System.out.println(blockMap.keySet());.

So, for some reason, I have a Map<Long, Integer> that contains two Longs with value 0 as key. And a keyset Set<Long> with two 0s. But map and set do not allow duplicate keys, how is this possible?


The code first adds a simple entry to the map:

blockMap.put(0L, i);

Then, I add another entry by converting the map to a JSON string, using GSON and then back to a map:

gson.toJson(blockMap);
...
Map map = gson.fromJson(json, Map.class);
blockMap.putAll(map);

I would expect that it overwrites the previous entry and not adds another entry with the same, duplicate, key.

Zabuza :

Explanation

You are interpreting your results slightly wrong. You say it prints

[0, 0]

And conclude that you have two keys of type Long with value 0. But that is actually wrong. One is indeed a Long, coming from here:

blockMap.put(0L, i);

But the other is a String "0", which, in the print, looks the same.


Raw-types

You might be confused why there is a string-key in your HashMap<Long, Block>. After all, it specifies the key as Long, not as String.

The issue is here:

Map map = gson.fromJson(json, Map.class);
blockMap.putAll(map);

What happens here is that you use putAll, but provide a Map. Note that it has no generics, it is a raw-type.

By using a raw-type, you basically sacrifice all type-safety and promise Java "Yeah, yeah, trust me, I know what I am doing.". So Java will just trust you that you actually give it a Map<Long, Block>, but you are actually giving it a Map<String, String>.


Heap pollution

In Java, generics are erased at runtime. Meaning that, after passing compilation, Java knows nothing about generics anymore and basically allows you to mix types as you want, it is just "Map<Object, Object>" more or less.

So you managed to trick Javas safe generic-type-system and injected a String into a map that only accepts Longs as keys. This is called heap pollution. And is the major reason why you should never ever use raw-types. There is no benefit in using raw-types.

A small example of heap pollution:

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);

Notes

Here is more to read about this topic:

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=11294&siteId=1