Here is what I know:
Double
is a subtype of Number
and List
is not a subtype of List
It helped me to see generics as constraints or contracts, not as types with subtypes.
So a variable List extends Number> var
says: var
is a list of some unknown type ?
, which is constrained to be a subtype of Number.
List listN;
List listD;
List extends Number> listX;
...
Number n1 = ...;
Double d1 = ...;
...
listN.add(n1); // OK n1 is a Number
listN.add(d1); // OK d1 is a Double, which is a Number
listD.add(n1); // compile error, n1 is not a Double
listD.add(d1); // OK
listX.add(n1); // compile error, because the exact type of list is not known! (prevents putting a Dog in a Cat list)
listX.add(d1); // compile error, same cause
So when you can't even put a Number into a List extends Number>
, whats the purpose of such a list? It allows you to work with lists of which the exact type does not matter for the task at hand:
// instead of several exactly typed methods...
int count(List numberList) {...}
int count(List
Using wildcards ? extends X
allows to relax rigid contracts to weaker conditions.
Using a named type parameter, you can establish constraints on allowed types between several variables:
// works for any type T of list, whatever T is
// T is the same in the argument and in the return
T pickObject(List list, int index) {
return list.get(index);
}
// works for any type T of list, if T is a Number type
// T is the same in the argument and in the return
T pickNumber(List list, int index) {
return list.get(index);
}
...
List list;
Number n = pickNumber(list);