2

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.

Zabuzard
  • 20,717
  • 7
  • 45
  • 67
martse
  • 23
  • 6
  • 5
    Can you post a [mcve]? – Sweeper Feb 13 '20 at 19:36
  • I disagree. I just tried exactly the provided code and this prints out what the op said. – Schottky Feb 13 '20 at 19:55
  • @Schottky OPs code is **not complete**, how could you execute it *as-it-is*? `Block` and the JSON payload are missing. Please share the full code, then others can also reproduce and investigate. – Zabuzard Feb 13 '20 at 19:58
  • What is `Block` class? – Arvind Kumar Avinash Feb 13 '20 at 19:59
  • I used Block as a simple class with two properties string and a long. getToken() simply returns the long and I can reproduce the duplicate key issue – Schottky Feb 13 '20 at 20:02
  • I removed the block class completely and made things simpler, how do i post new code here – martse Feb 13 '20 at 20:02
  • You are using `putAll` but providing a raw `Map` without generics. That is dangerous and you should be get serious warnings for that. Generics are erased at runtime. This could allow you to pollute your map with, lets say `Integer`s instead of `Long`s. So maybe one of your `0` is a `Long` while the other is `Integer`, who knows. Just guessing here. One of the reasons you should never ever use raw types. – Zabuzard Feb 13 '20 at 20:03
  • @martse Just [edit] your question. – Zabuzard Feb 13 '20 at 20:03
  • Zabusa i think you are right, i should not provide raw Map. Still it is interestring that this happens. – martse Feb 13 '20 at 20:07
  • 2
    @Zabuza that is the issue. Using this implementation, one key is a String and another key is a Long – Schottky Feb 13 '20 at 20:08
  • Does hashmap allow one key to be string when it is defined that key should be long? – martse Feb 13 '20 at 20:09
  • Anyway thanks for your help guys, i will change some things. – martse Feb 13 '20 at 20:10
  • 1
    You basically bypassed the generic-system. So with this, you can basically insert whatever you want in your map, not only `Long`. The map works the same still though, it will not allow duplicates. But by being able to insert whatever you want, you can easily insert stuff that looks similar but is actually different, like a string `"0"` vs a long `0` vs an integer `0` vs a short `0`, ... all would be considered different. – Zabuzard Feb 13 '20 at 20:24

3 Answers3

3

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:

Zabuzard
  • 20,717
  • 7
  • 45
  • 67
2

The issue here is as @Zabuza pointed out, that you are using a raw Map without generics that are erased at runtime. Just try out this example:

for (Object key: Util.blockMap.keySet()) {
    System.out.println("key: " + key + " type: " + key.getClass());
}

and you will see that your first key is of type Long while the second key is of type String

Since the equals-method is defined inside String as:

if (anObject instanceof String) {
    // ...
}
return false;

equals will always return false

Schottky
  • 250
  • 1
  • 6
1

I hope the following code will clear your doubt:

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

Output with this code:

[0, 0]
class java.lang.Long
class java.lang.String
Arvind Kumar Avinash
  • 50,121
  • 5
  • 26
  • 72