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.
return someMap; //compilation fails
– BucksawMap<?, ? extends Map<?, ?>>
– ReinMap
. Also i didn't find the answer in that 'List posts' – Bucksaw