Map<String, Map<String, Boolean>> myMap = new HashMap<String,HashMap<String,Boolean>>(); - java

Why doesn't that work in java, but this does
Map<String, Map<String, Boolean>> myMap = new HashMap<String,Map<String,Boolean>>();
Just to clarify the below alteration of the nested HashMap shows a compiler error, whereas the above does not not; with a Map (not hashmap)
Map<String, Map<String, Boolean>> myMap = new HashMap<String,HashMap<String,Boolean>>();

This is because generics in Java are invariant, i.e. even if class B is an A, a Collection<B> is not a Collection<A>.
And this is for a good reason. If your example were legal, this would be possible:
Map<String, HashMap<String, Boolean>> myHashMap = new HashMap<String,HashMap<String,Boolean>>();
Map<String, Map<String, Boolean>> myMap = myHashMap;
myMap.put("oops", new TreeMap<String, Boolean>());
HashMap<String, Boolean> aHashMap = myMap.get("oops"); // oops - ClassCastException!

In the second case myMap is a map which keys are of type String and values are of type Map<String, Boolean>. HashMap<String, Boolean> is not a Map<String, Boolean> it implements it. Therefore, this will compile:
Map<String, ? extends Map<String, Boolean>> myOtherMap =
new HashMap<String,HashMap<String,Boolean>>();

I think that's because of the difference between Map<String, Boolean> and HashMap<String,Boolean>.
Indeed, the generics are here a specification, which must be the same on both sides. (or at least that's my opinion).

Related

Java Map.getOrDefault with bounded wildcard

Got a Map<String, ? extends Map<String, Integer>> mapOfMaps variable.
Map<String, Integer> result = mapOfMaps.get("aaa");
works, but
Map<String, Integer> result = mapOfMaps.getOrDefault("aaa",Collections.emptyMap());
says
The method getOrDefault(Object, capture#1-of ? extends Map<String,Integer>) in the type Map<String,capture#1-of ? extends Map<String,Integer>> is not applicable for the arguments (String, Map<String,Integer>)
the same goes for
Map<String, Integer> result = mapOfMaps.getOrDefault("aaa",Collections.<String,Integer>emptyMap());
or
Map<String, Integer> result = mapOfMaps.getOrDefault("aaa",(Map<String,Integer>)Collections.EMPTY_MAP);
or even
Map<String, Integer> result = mapOfMaps.getOrDefault("aaa",new HashMap<String, Integer>());
Is there a way of using the getOrDefault like that or do I have to use the clunky way ?
Map<String, Integer> result = mapOfMaps.get("aaa");
if( result == null ) {
result = Collections.emptyMap();
}
You can use Collections.unmodifiableMap to view your map as Map<String, Map<String, Integer>>.
Map<String, ? extends Map<String, Integer>> mapOfMaps = new HashMap<>();
Map<String, Map<String, Integer>> view = Collections.unmodifiableMap(mapOfMaps);
Map<String, Integer> map = view.getOrDefault("foo", Collections.emptyMap());
In a single line, however, it still looks ugly, since you need to specify the generic type arguments for unmodifiableMap.
Map<String, Integer> map = Collections.<String, Map<String, Integer>>
unmodifiableMap(mapOfMaps).getOrDefault("foo", Collections.emptyMap());
Explanation
You cannot call any method that has an unbounded or extends-bounded wildcard parameter, because the exact type of the wildcard is not known at compile time.
Let's make this simpler and look at Map<String, ? extends Number>, to which you could assign either of
Map<String, ? extends Number> map = new HashMap<String, Integer>();
Map<String, ? extends Number> map = new HashMap<String, Double>();
However, when calling map.getOrDefault(Object k, V defaultValue), there is no way to determine the type for defaultValue at compile time, since the actual type may change at runtime, even for the very same assignment (not the same instance though).
// compile-time error, could require a Double or any other Number-type
Number i = map.getOrDefault("foo", (Number)Integer.MAX_VALUE);
One possible, but still rather clunky, solution is a helper function:
static <K1, K2, V, M extends Map<K2, V>> Map<K2, V> getOrEmpty(Map<K1, M> mapOfMaps, K1 key) {
Map<K2, V> submap = mapOfMaps.get(key);
return submap != null ? submap : Collections.emptyMap();
}
and then call it like
Map<String, Integer> result = getOrEmpty(mapOfMaps,"aaa");
But I would still prefer a solution without having to define an extra function.

JAVA: How to pass on arguments of a map to be the value associated to the ID of another parent map

I'm not too clear on the terminology, so excuse the title.
What I'm looking for is the answer on how to do this:
This is the map:
Map<String, Map<String, String>> theMap = new HashMap<String, Map<String, String>>();
And this is the way I'm trying to add to it, I hope it provides enough insight:
theMap.put("string", {"a"="b"});
(that doesn't work)
If you want to add a Map<String, String> to a Map<String, Map<String, String>> the code below is the way to go:
Map<String, Map<String, String>> theMap = new HashMap<String, Map<String, String>>();
Map<String, String> innerMap = new HashMap<String, String>();
innerMap.put("a", "b");
theMap.put("string", innerMap);
We create a variable innerMap of type Map<String, String> and simply add it to the theMap object.
You need to add subMap to theMap object :
Map<String, Map<String, String>> theMap = new HashMap<>();
Map<String, String> subMap = new HashMap<>();
subMap.put("a","b");
theMap.put("string",subMap);
The value is Map<String, String>, not String. You need to add new Map and insert the values to this Map
theMap.put("string", new LinkedHashMap<String, String>() {{
put("a","b");
}});

Why <String, String> entry allowed for HashMap<Integer, String>()?

I have HashMap which generic type <Integer, String> i.e. key should be an Integer and value should be String for this HashMap.
I wrote bellow code which put String and getting no compilation and runtime error. Why?
Map map = new HashMap<Integer, String>();
map.put("a", "one");
System.out.println(map);
OUTPUT:
{a=one}
I have HashMap which generic type <Integer, String> ...
No you do not!
Map map = new HashMap<Integer, String>();
Means you have just a Map (because of Map map =). If you want Map<Integer, String> you must use:
Map<Integer, String> map = new HashMap<Integer, String>();
or, in later versions of Java
Map<Integer, String> map = new HashMap<>();
Added
The reason for this is that the right-hand-side of the assignment is a separate process and is evaluated first. In your case it creates a HashMap<Integer, String>.
Next the assignment happens, the compiler checks that HashMap<Integer, String> can be cast to Map (which is equivalent to Map<Object,Object> BTW) and the assignment is performed. From then on all references to map treat it as type Map<Object,Object> and can therefore hold any type for key or value.
You are adding content to Map map declared without specifying any generics types.
If you declare the map this way the compilator doesn't know how to check the map content.
If you change your map declaration to
Map<Integer, String> map = new HashMap<>();
Then you will have a compilation error.
Map map = new HashMap<Integer, String>();
Here your definition is type specified, however declaration is not. So, you are able to add any type to map.
The proper way for generic map declaration is
Map<Integer, String> map = new HashMap<Integer, String>();
or in new versions of Java, you can skip type in defintion.
Map<Integer, String> map = new HashMap<>();
Defining generics on the right side is more or less obsolete (grey font).
Following code wouldn't compile:
Map<Integer, String> map = new HashMap<>();
map.put("a", "one");
System.out.println(map);
with this explanation:
Wrong 1st argument type. Found: 'java.lang.String', required: 'java.lang.Integer'

What is not valid in this assignment: `Map<String, Object> mObj = new HashMap<String, String[]>();`?

in java, what is wrong with this assignment:
Map<String, Object> mObj = new HashMap<String, String[]>();
I get:
error: incompatible types: HashMap<String,String[]> cannot be converted to Map<String,Object>
Since String[] is an Object, that should work.
If I cast to an unparameterized Map like this: Map<String, Object> mObj = (Map) new HashMap<String, String[]>();, it is working but of course, I get a warning and it is dirty.
Further more, I feel that my first assignment should work.
Thank you !
PS: I cannot simply change new HashMap<String, String[]>(); to new HashMap<String, Object>(); because in reality, I call a method that returns a Map<String, String[]>(); and of course, I cannot change this method. Thank you again.
The error is is because generics do not support subtyping. Number a = new Integer(5) is valid case. But once you put generics it gives compilation error ArrayList<Number> a = new ArrayList<Integer>() is not allowed. See if this link helps https://dzone.com/articles/5-things-you-should-know-about-java-generics to understand some guidelines on Generics.
Lets' see what could happen if what you wrote would be possible:
HashMap<String, String[]> foo = new HashMap<String, String[]>();
Map<String, Object> bar = foo;
bar.put("key",new Object());
String[] baz = foo.get("key"); // <-- ClassCastException
See the problem? A series of normal, mundane operations will cause ClassCastException in a place where you would expect it wouldn't be possible to emit one.
Edit: To summarize, Map<String, Object> is not a supertype of Map<String, String>, Map<String, Object[]> or Map<String, String[]>, so the assignment will not work, as the types aren't compatible. This is where wildcards come in; see this answer and this one too
Do this:
Map<String, ? extends Object> mObj = new HashMap<String, String[]>();
But I can't tell you why your solution doesn't work.

Nested HashMaps and Declaration

I'm trying to experiment with Maps and I have this doubt:
Map<String, Object> input = new LinkedHashMap<String, Object>();
String operator = "in";
String argument = "foo";
String field = "AvailabilityStatus";
Map<String, Object> innerMap = new LinkedHashMap<String, Object>();
innerMap.put(operator, argument);
input.put(field, innerMap);
The function call for the above code is
String output = FunctionA(input);
Seems to work fine but changing the input to:
Map<String, Map<String, Object>> input = new LinkedHashMap<String, LinkedHashMap<String, Object>>();
doesn't let me call the function the same way. The functionA is:
public static String FunctionA(Map<String, Object> filters) throws Throwable {
//logic goes here
}
Aren't the two statements essentially trying to do the same thing?
Alternately, you could make the FunctionA method like this:
public static String FunctionA(Map<String, ? extends Object> filters) throws Throwable{
//logic goes here
}
Doing this will be happy then!
FunctionA(new HashMap<String, LinkedHashMap<String, Object>>());
A Map<String, Map<String, Object>> is not a subtype of Map<String, Object>, even though Map<String, Object>is a subtype of Object.
Indeed, uou can store whatever object you want in the latter, whereas you can only store Map<String, Object> instances in the former. That's why the compiler doesn't let you pass a Map<String, Map<String, Object>> to a method taking a Map<String, Object> as argument.
If it let you do that, the method could store Strings, Integers or Bananas into the map, which would thus ruin the type-safety of the map, supposed to only contain instances of Map<String, Object>.

Categories