Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java excplicit cast of nested maps

Why does this cast work?

import java.util.HashMap;
import java.util.Map;

public class TestMap {
    public static void main(String[] args) {
        Map<String, Map<String, Map<String, Map<String,Integer>>>> resultMap = new HashMap<>();
        Map<String, Object> aMap = new HashMap<String, Object>();
        Map<String, Integer> hiddenMap = new HashMap<String, Integer>();
        hiddenMap.put("fortytwo", 42);
        aMap.put("key", hiddenMap);
        resultMap =  (Map<String, Map<String, Map<String, Map<String, Integer>>>>) aMap.get("key");
        System.out.println(resultMap);
    }
}

also this:

Map<String, Map<String, Map<String, Map<String,Map<String,Integer>>>>> resultMap = new HashMap<>();
...
resultMap =  (Map<String, Map<String, Map<String, Map<String,Map<String,Integer>>>>>) aMap.get("key");

and so on...

How does this happen that the hidden map which is Map<String, Integer> gets successfully cast to Map<String, Map<String, Map<String, Map<String,Integer>>>> resultMap?

Always prints:

{fortytwo=42}

Also this works (Map instead of Map):

public static void main(String[] args) {

        Map<String, Map<String, Map<String, Map<String,Map<String,Integer>>>>> resultMap = new HashMap<>();
        Map<String, Map> aMap = new HashMap<String, Map>();
        Map<String, Integer> hiddenMap = new HashMap<String, Integer>();
        hiddenMap.put("fortytwo", 42);
        aMap.put("key", hiddenMap);
        resultMap =  (Map<String, Map<String, Map<String, Map<String,Map<String,Integer>>>>>) aMap.get("key");
        System.out.println(resultMap);

    }

EDIT: So as @shizhz says, it is because of Type Erasure of course! So the code above is equivalent to:

Map resultMap = new HashMap();
Map aMap = new HashMap();
Map hiddenMap = new HashMap();
hiddenMap.put("fortytwo", 42);
aMap.put("key", hiddenMap);
resultMap = (Map) aMap.get("key");

Which also works

like image 664
ACV Avatar asked Nov 29 '25 06:11

ACV


1 Answers

Because java generics is used at compile time to provide tighter type checks, the type parameter is erased by compiler according Type Erasure rules:

  • Replace all type parameters in generic types with their bounds or Object if the type parameters are unbounded. The produced bytecode, therefore, contains only ordinary classes, interfaces, and methods.
  • Insert type casts if necessary to preserve type safety.
  • Generate bridge methods to preserve polymorphism in extended generic types.

In code Map<String, Map> aMap = new HashMap<String, Map>();, the value in aMap is a raw type Map, which means the compiler has no idea what's the type it contains, when you try to cast a raw type of Map to any generics type of Map like Map<String, Integer>, the best compiler can do is giving you a warning. The generic type is erased at compile time and type cast will be generated when you get value from a generic map, so you can only get a runtime ClassCastException exception if the type mismatchs.

Let's have a look at the following example:

public static void main(String[] args) {
    Map map = new HashMap();

    map.put("hello", "world");
    map.put(new Integer(1), 1);
    map.put(new Object(), Lists.newArrayList("hello"));

    Map<String, Integer> m =  (Map<String, Integer>) map;
    System.out.println(m);

    Integer i = m.get("hello");// ClassCastException happens at here at runtime
}

I'm trying to convert a Map containing all kinds of keys and values to Map<String, Integer> but there's no compile error, after type erasure, the above code is actually equivalent to:

public static void main(String[] args) {
    Map map = new HashMap();

    map.put("hello", "world");
    map.put(new Integer(1), 1);
    map.put(new Object(), Lists.newArrayList("hello"));

    Map m = (Map) map;
    System.out.println(m);

    Integer i = (Integer)m.get("hello");
}

Now you can easily tell why the last line caused ClassCastException.

like image 74
shizhz Avatar answered Nov 30 '25 20:11

shizhz