How to declare generic Map of Map - java

How to declare such a Map where instead of Object I have specific type:
Map<Class, Map<String, ClassInstance>> map;
Such that could be used as:
Map<String, new Type()) valueMap = new HashMap();
map.put(Type.class, valueMap);
The problem is I can't figure out how to declare generic type of both 'Class' and 'ClassInstance'.

Map<Class<?>, Map<String, Object>> map;
You cannot statically enforce that the Object is of the given type. That's for your code to enforce.
Map<String, Object> valueMap = new HashMap<>();
valueMap.put("Foo", new Type());
map.put(Type.class, valueMap);

Related

Map of Map iteration

Im storing 2 map with different structure in single map like below,
Map<String, List<String>> colMap = new HashMap<String, List<String>>();
Map<String, String> appMap = new HashMap<String, String>();
// colMap assigning some values
// appMap assigning some values
Map<String, Map> mainMap = new HashMap<String, Map>();
mainMap.put("appMap", appMap);
mainMap.put("colMap", colMap);
I want to get map one by one and iterate the map.
If I try get map like below, getting error,
.......
Map colMap = map.get("colMap");
for(Entry<String, List<String>> entry : colMap.entrySet())
Error: Type mismatch: cannot convert from element type Object to Map.Entry<String,List<String>>
Why not just create a simple container POJO class (or record in Java 16+) for the two maps instead of mainMap and keep the relevant type-safety which to do it Java-way?
public class MapPojo {
private final Map<String, List<String>> colMap;
private final Map<String, String> appMap;
public MapPojo(Map<String, List<String>> colMap, Map<String, String> appMap) {
this.colMap = colMap;
this.appMap = appMap;
}
// getters, etc.
}
MapPojo mainMap = new MapPojo(colMap, appMap);
Error you are getting because when you are doing map.get operation your reference is Just Map without any Generics which will treated as Object class's reference. You should use generics like below and it will work -
Map<String, List<String>> colMap = map.get("colMap");
for(Entry<String, List<String>> entry : colMap.entrySet())

Cast safely a sessionScope value in Java

I have a Map object stored in a sessionScope. If I cast it to the following object:
Map<String,Object> mapItem = (Map<String, Object>) entry.getValue();
I get a Type safety warning. Type safety:
Unchecked cast from Object to Map<String,Object>
I tried to cast the scope variable to an object and check what instance the object is but then I can not directly check if it is of Map<String,Object>. So I wonder how I should handle this further?
Object obj = entry.getValue();
if(obj instanceof Map<?, ?>) {
//not sure what to do here
}
Any help is highly appreciated!
I think the issue is with the generics applied to the original Map your Entry is from. Consider these examples
Map<String, Object> map = new HashMap<>();
for (Map.Entry<String, Object> entry : map.entrySet()) {
Map<String, Object> mapItem = (Map<String, Object>) entry.getValue(); // Will have unchecked warning
}
// Declare another map specifying the values type more specifically
Map<String, Map<String, Object>> map2 = new HashMap<>();
for (Map.Entry<String, Map<String, Object>> entry : map2.entrySet()) {
Map<String, Object> mapItem = entry.getValue(); // Will not require cast
}
The issue occurs because the compiler doesn't know anything more about the values type than its an Object. You know its a Map<String, Object> so you can tell the compiler that with generics and it will enforce that in the rest of the code.
It's not easy to achieve that;
Look at how to handle it in Gson:
Type mapType = new TypeToken<Map<String, Object>>(){}.getType();
new Gson().fromJson("{}", mapType);

How to correctly define a multidimensional generalized Collection in Java?

For example, I want to create a map that is a map of a map of a map. So I have declared the map as so. From a resultset I want to create a JSON object.
Map<String, Map<String, Map<String, String>>> data = new LinkedHashMap<String, Map<String, Map<String, String>>>();
I could define as
Map<String, Object> map = new LinkedHashMap<String, Object>();
however I need to put items one at a time so I get the map I want and put it in. Then the same the following iterations so I would have to cast the object to the map which leaves unchecked warnings.
Is there a better way of declaration of this type of Collection?
You can use a Google Guava MultiMap, but really you're better off creating a new Object and referencing the entire collection of data from the single key.
public class MyObjectData {
private String string1;
private Map<String, String> map;
// Getters and setters
}
Then your map becomes Map<String, MyObjectData>

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
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.
Since you've declared aMap as Map<String, Object>, the compiler cannot tell if the values won't indeed be of type Map<String, Map<String, Map<String,Integer>>>. It will just give you an "Unchecked cast" warning to let you think about the consequences.
The cast works unless you're actually trying to do something with the values:
resultMap.get("fortytwo").isEmpty();
will result in
Exception in thread "main" java.lang.ClassCastException:
java.lang.Integer cannot be cast to java.util.Map
If you had declared aMap as Map<String, Map<String, Map<String, Map<String, Map<String, Integer>>>>> you wouldn't be able to put hiddenMap in it in the first place.

HashMap / TreeSet combination inconsistency

This works ok:
Map aMap;
aMap = new HashMap<String, TreeSet<String>>();
This does not compile:
Map<String, Set<String>> aMap;
aMap = new HashMap<String, TreeSet<String>>();
Error message:
Compilation failed (26/05/2014 11:45:43) Error: line 2 - incompatible types -
found java.util.HashMap<java.lang.String,java.util.TreeSet<java.lang.String>>
but expected java.util.Map<java.lang.String,java.util.Set<java.lang.String>>
Why?
The first one works because you use a raw type (without generic) so you can put any type of map in there.
The second one doesn't work because a XXX<Set> is not a XXX<TreeSet>.
So you need to choose between:
Map<String, Set<String>> aMap = new HashMap<String, Set<String>>();
//or
Map<String, TreeSet<String>> aMap = new HashMap<String, TreeSet<String>>();
And in both case you will be able to write:
aMap.put("abc", new TreeSet<>());
The main difference is when you get an item from the map, with the former construct you won't have access to the TreeSet specific methods.
Finally, with Java 7+ you can omit the generic information on the right hand side and the compiler will determine it automatically for you:
Map<String, Set<String>> aMap = new HashMap<>();
Map<String, TreeSet<String>> aMap = new HashMap<>();
Use this instead:
Map<String, ? extends Set<String>> aMap;
aMap = new HashMap<String, TreeSet<String>>();
Because the Set's generic must not be the same than TreeSet's generic.
+1 to Peter's answer, TreeSet implements SortedSet which extends Set.
Map<String, ? extends Set<String>> aMap;
aMap = new HashMap<String, TreeSet<String>>();
will work fine.

Categories

Resources