public class Foo<S,T extends S> {
...
}
declares two type parameters S and T for class
Foo, but requires in addition that, in any instantiation,
T must be a subtype of S. (It can be expected that
this relation between the two types will be exploited in the code
defining the class Foo, otherwise it would be pointless.)
Here is a more concrete example in which one of the types is constant.
public class SortedList<E extends Comparable> {
public void insert(E item);
...
}
The elements of a sorted list (or priority queue or binary search tree
etc.) must be comparable with respect to order, since this will be
assumed in the insertion routine. The type declaration ensures this
by requiring that elements of type E implement the
Comparable interface:
public interface Comparable {
public int compareTo(Object o);
}
It is instructive to compare this with the earlier approach to such classes. Previously a generic sorted list might be defined by
public class SortedList {
public void insert(Comparable item);
...
}
This will ensure that any object inserted into the list implements a
compareTo method. But it does not ensure that it is the
same relation. For example:SortedList list = new SortedList(); list.insert(new Integer(0)); list.insert(new Float(0.0));will succeed at compile-time but lead to a ClassCastException at run-time, when an attempt is made to compare the order of an Integer and a Float.
If instead, in Java 5, we use the definition
public class SortedList<E extends Comparable> {
public void insert(E item);
...
}
then the compiler will force the programmer to decide betweenSortedList<Float> list = new SortedList<Float>();and
SortedList<Integer> list = new SortedList<Integer>();so that one or other of the lines
list.insert(new Integer(0)); list.insert(new Float(0.0));will already fail at compile-time.