Usually Java tutorials say you can't add anything to a List<? extends Something>
because the compiler can't know the effective type in the list.
I found this explanation non intuitive, and also not strictly true, because it pretends the compiler is a smart being, which understands that a List
is an ordered container of elements and prevents you from doing potentially unsafe things. In reality, a compiler is just a program, and it simply obeys some rules.
So, it's best to do some role playing, and thinking like a compiler. You have
interface List<E> {
void add(E element);
}
Note that <E>
doesn't provide any extra semantic to the compiler, in the sense that it doesn't know that you are defining a container type. You could define a Asdf<Q>
, and for the compiler it doesn't matter, or an Elephant<W>
and still the same rules apply (and clearly Elephant
is not a container type, ie you don't add anything to an elephant - I hope...)
So you declare a reference of type List<? extends Shape>
. The compiler understands something similar
abstract class Unknown extends Shape {}
class ListOfUnknown {
void add(Unknown element) {}
Unknown get(int index) {}
}
Can you see why you can't add a Rectangle
to such a list? According to normal Java rules, you can supply a Rectangle
to a method such as add(Shape)
because their interfaces are assured to be compatible by the subtyping relation.
To be allowed to invoke add(Unknown)
with an argument of type Rectangle
, Rectangle
should be a child of Unknown
, but Rectangle
only extends Shape
, so there is nothing really special to generics here, it's the normal Java rule for type compatibility. To be able to call add(Unknown)
you'd need a reference to an object of type Unknown
or equivalent (some class which extends Unknown
), but as you can see there is no such type defined anywhere, so this eventually prohibits to add()
anything to this list.
When studying generics, always ask yourself "Why?" and never stop at the List
examples because, even if generics were primary added for collections, they are a language feature, so you must understand the semantics they carry on, and not concentrating on the specific implementation of a container type.