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<Number> listN;
List<Double> 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<Number> numberList) {...}
int count(List<Object> objectList) {...}
// ...etc. you can have one with a degree of freedom:
int count(List<?> anyList) {...} // don't need to know the exact type of list
// instead of this...
Number sum(List<Number> numberList) {...}
Number sum(List<Double> doubleList) {...}
Number sum(List<Integer> integerList){...}
// you can do this, with a little less freedom in the ?
Number sum(List<? extends Number> list) {
// the only thing we need to know about the list's type is that it is some number type
...
Number ni = list.get(i);
...
}
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> T pickObject(List<T> 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 extends Number> T pickNumber(List<T> list, int index) {
return list.get(index);
}
...
List<Number> list;
Number n = pickNumber(list);
Double
andInteger
in the sameList<? extends Number>
list. BothList<Double>
andList<Integer>
areList<? extends Number>
, butList<? extends Number>
is not the same asList<Number>
. – GrandpaDog
presumably extendsAnimal
. – Grandpa