Nested wildcards
Solution 1:
It is important to understand the implication of the wildcard types.
You already understood that you can assign your Map<Integer, Map<Integer, String>>
to Map<?, ?>
as Map<?, ?>
implies arbitrary types, unknown to whoever might have a reference of the declared type Map<?, ?>
. So you can assign any map to Map<?, ?>
.
In contrast, if you have a Map<?, Map<?, ?>>
it has an unknown key type but the value type is not unknown. It’s Map<?,?>
the type, recall the information above, that can be assigned with any map.
So, the following code is legal:
Map<?, Map<?, ?>> map=new HashMap<>();
map.put(null, Collections.<String,String>singletonMap("foo", "bar"));
map.put(null, Collections.<Double,Integer>singletonMap(42.0, 1000));
map.put(null, Collections.<Object,Boolean>singletonMap(false, true));
Here, we are putting a null
key as we can’t put
anything else for keys but arbitrary typed maps as values as that’s what a value type of Map<?, ?>
implies: can be assigned from arbitrary maps. Note that by iterating over the entries we can also set other entries having non-null
keys to arbitrary maps then.
So I’m quite sure that you don’t want to assign your Map<Integer, Map<Integer, String>>
to a Map<?, Map<?, ?>>
and discover arbitrary maps not being Map<Integer, String>
as values afterwards and that you are quite happy that the compiler doesn’t allow this.
What you actually want to do is to assign your map to a type which has both, key and value type, unknown but still telling that your values are maps:
Map<Integer, Map<Integer, String>> someMap = new HashMap<>();
Map<?, ? extends Map<?, ?>> map=someMap;
In the generic type system Map<Integer, String>
is a sub-type of Map<?, ?>
so you can assign it to Map<?, ?>
as well as ? extends Map<?, ?>
. This sub-type relationship is not different than the relationship of String
to Object
. You can assign any String
to a variable of type Object
but if you have a Map<?,String>
you can’t assign it to Map<?,Object>
but only to Map<?, ? extends Object>
for the same reason: the map shall continue to contain String
s as values rather than receiving arbitrary objects.
Note that you can workaround this limitation. You can say:
Map<Integer, Map<Integer, String>> someMap = new HashMap<>();
Map<?, Map<?, ?>> map=Collections.unmodifiableMap(someMap);
Since the map returned by unmodifiableMap
does not allow any modifications, it allows widening the key and value types. The contained values are of the specified type (i.e. Map<?, ?>
) when you query the map, but attempts to put in arbitrary map values, while not rejected by the compiler, will be rejected at runtime.
Solution 2:
The short answer is that generics are invariant, so this will not work.
The long answer takes a while to understand. It starts simple:
Dog woof = new Dog();
Animal animal = woof;
Works just fine, since a Dog
is an Animal
. On the other hand:
List< Animal > fauna = new ArrayList<>();
List< Dog > dogs = new ArrayList<>();
fauna = dogs;
will fail to compile, because generics are invariant; basically a List<Dog>
is not a List<Animal>
.
How come? Well if the assignment would have been possible, what is stopping you from doing:
fauna.add(new Cat());
dogs.get(0); // what is this now?
A compiler could be smarter here, actually. What if your Lists are immutable? After creation, you can't put anything into them. In such a case, fauna = dogs
, should be allowed, but java does not do this (scala does), even with the newly added Immutable collections in java-9.
When Lists are immutable, they are said to be Producers
, meaning they don't take the generic type as input. For example:
interface Sink<T> {
T nextElement();
}
Since Sink
never takes T
as input, it is a Producer
of T
s (not a Consumer
), thus it could be possible to say:
Sink<Object> objects ...
Sink<String> strings ...
objects = strings;
Since Sink
has no option to add elements, we can't break anything, but java does not care and prohibits this. kotlinc
(just like scalac
) allows it.
In java this shortcoming is solved with a "bounded type":
List<? extends Animal> animals = new ArrayList<>();
animals = dogs;
The good thing is that you still can't do: animals.add(new Cat())
. You know exactly what that list holds - some types of Animals, so when you read from it, you always, for a fact, know that you will get an Animal
. But because List<? extends Animal>
is assignable to List<Dog>
for example, addition is prohibited, otherwise:
animals.add(new Cat()); // if this worked
dogs.get(0); // what is this now?
This "addition is prohibited" is not exactly correct, since it's always possible to do:
private static <T> void topLevelCapture(List<T> list) {
T t = list.get(0);
list.add(t);
}
topLevelCapture(animals);
Why this works is explained here, what matters is that this does not break anything.
What if you wanted to say that you have a group of animals, like a List<List...>
? May be the first thing you want to do is List<List<Animal>>
:
List<List<Animal>> groups = new ArrayList<>();
List<List<Dog>> dogs = new ArrayList<>();
groups = dogs;
this would obviously not work. But what if we added bounded types?
List<List<? extends Animal>> groups = new ArrayList<>();
List<List<Dog>> dogs = new ArrayList<>();
groups = dogs;
even if List<Dog>
is a List<? extends Animal>
the generics of these are not (generics are invariant). Again, if this would have been allowed, you could do:
groups.add(<list of cats>);
dogs.get(0); // obvious problems
The only way to make it work would be via:
List<? extends List<? extends Animal>> groups = new ArrayList<>();
List<List<Dog>> dogs = new ArrayList<>();
groups = dogs;
that is, we found a super type of List<Dog>
in List<? extends Animal>
and we also need the bounded type ? extends List...
so that the outer lists themselves are assignable.
This huge intro was to show that:
Map<Integer, Map<Integer, String>> map = new HashMap<>();
Map<?, ?> broader = new HashMap<>();
broader = map;
would compile because there are no restrictions what-so-ever here, the broader
map basically is a map "of anything".
If you read what I had to say above, you probably know why this is not allowed:
Map<Integer, Map<Integer, String>> map = new HashMap<>();
Map<?, Map<?, ?>> lessBroader = new HashMap<>();
lessBroader = map;
if it would have been allowed, you could do:
Map<Double, Float> newMap = new HashMap<>(); // this is a Map<?, ?> after all
lessBroader.add(12, newMap);
map.get(12); // hmm...
If maps were immutable and the compiler would care, this could have been avoided and the assignment could have made to work just fine.