Take two classes: Fruit, and Apple which extends Fruit>. The following will work, because Apple extends Fruit.
List<Fruit> fruitList = new ArrayList<Fruit>();
fruitList.add(new Fruit()); // Okay - the same type
fruitList.add(new Apple()); // Okay - Apple subclass of fruit
In the above example, we specified that we have a list of Fruit objects, which includes sub-classes of Fruit such as Apples. So now take the logic one step further and declare a list that can contain anything that extends Fruit using bounded wildcards.
The first attempt below fails because the compiler hasn’t got a concrete type which it can instantiate:
// Will fail - doesn't know how to instantiate this list
List<? extends Fruit> moreFruit = new ArrayList<? extends Fruit>();
Therefore, if you give the compiler a concrete type to instantiate, you do get a valid list... sort of:
List<? extends Fruit> apples = new ArrayList<Apple>(); // Okay - Apples extends fruit
...but if you try adding objects to this list you’ll come unstuck:
apples.add(new Fruit()); // Error - incompatible types of '? extends Fruit' and 'Fruit'
apples.add(new Apple()); // Error - incompatible types of '? extends Fruit' and 'Apple'
… which means that you have a list to which nothing can be added - except null:
apples.add(null); // Okay
The reason for this seems to be that in declaring List<? extends Fruit> you are saying that you want a list that can hold anything that’s a sub-class of Fruit, including, for example, Oranges. The concrete implementation is Apples and you don’t know whether or not you have Apples or Oranges until run-time. This defeats one of the main points of Generics: using compile time checking, rather than run-time checking and so the compiler disallows this sort of code.