Found fact about unbounded wildcards that is annoying me. For example:
public class Test {
private static final Map
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
is not a List
.
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 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
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
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 void topLevelCapture(List 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
? May be the first thing you want to do is List
:>
List> groups = new ArrayList<>();
List> dogs = new ArrayList<>();
groups = dogs;
this would obviously not work. But what if we added bounded types?
List> groups = new ArrayList<>();
List> dogs = new ArrayList<>();
groups = dogs;
even if List
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();
dogs.get(0); // obvious problems
The only way to make it work would be via:
List extends List extends Animal>> groups = new ArrayList<>();
List> dogs = new ArrayList<>();
groups = dogs;
that is, we found a super type of List
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> 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> map = new HashMap<>();
Map, Map, ?>> lessBroader = new HashMap<>();
lessBroader = map;
if it would have been allowed, you could do:
Map 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.