For many applications this declaration of SortedList will be adequate. However, it is not difficult to see that the declaration
public class SortedList<E extends Comparable<E>> {...}
is in fact more restrictive than the raw declaration
public class SortedList<E extends Comparable> {...}
The latter condition is satisfied if there is some type X
such that E extends X and X implements
Comparable<X>. The former insists that X equals
E.
Here is an example to illustrate the issue. The definition
public abstract class Shape implements Comparable<Shape> {
public abstract int area();
public int compareTo(Shape other) {
return other.area() - this.area();
}
}
implies that Shapes have areas which can be used to order
them. Here are two particular kinds of Shape:
public class Square extends Shape {
private int side;
...
public int area() {
return side * side;
}
}
public class Rectangle extends Shape {
private int base, height;
...
public int area() {
return base * height;
}
}
each using its own appropriate definition of area().
Using either of the definitions
public class SortedList<E extends Comparable> {...}
or
public class SortedList<E extends Comparable<E>> {...}
we can construct sorted lists of Shapes, e.g. bySortedList<Shape> list = new SortedList<Shape>(); list.insert(new Square(1)); list.insert(new Square(2)); list.insert(new Rectangle(1,2));The reason is that Shape does indeed implement Comparable<Shape> and both Squares and Rectangles are legitimate values for Shape variables.
But suppose now we wish to construct a sorted list of Squares alone by the code
SortedList<Square> list = new SortedList<Square>(); list.insert(new Square(1)); list.insert(new Square(2));where you should note the change in the first line of code, from before. This is legitimate using the raw definition
public class SortedList<E extends Comparable> {...}
but not legitimate according to
public class SortedList<E extends Comparable<E>> {...}
The reason is that the compiler will accept that Square
implements Comparable, as a raw untyped interface, because
Square extends Shape and Shape implements
Comparable<Shape>. But the compiler will not accept
that Square implements Comparable<Square> because it
was not declared to do so.
This may seem perverse. A Square is comparable to any Shape hence a fortiori to any Square. So why does Square not implement Comparable<Square>? The answer is that Java 5 does not undertake to deduce any such rules about the relations between types. If G is some generic type declaration, no subtype relations between G<S> and G<T> are inferred by the compiler from subtype relations between S and T.
There is, however, a simple, if visually unappealing, way around. The reason why
public class SortedList<E extends Comparable> {...}
permits the construction of SortedList<Square> is that the,
now deprecated, raw bound <E extends Comparable> is satisfied
by a type E if there is some type X such that
E extends X and X implements
Comparable<X>. Java 5 has an approved syntax that permits
the expression of exactly such a bound, consistently with full typing
of the Comparable interface, namely
public class SortedList<E extends Comparable<? super E>> {...}
The expression ? is a wildcard which is matched by any type.
This means that ? super E is matched by any type which is a
supertype of E, so thatE extends Comparable<? super E>is matched by a type E if there is some type X such that E extends X and X implements Comparable<X>. This is exactly what is needed. It follows that SortedList<Square> is now a legitimate type because Square extends Shape and Shape implements Comparable<Shape>.
Whew!
The issues discussed here are mainly of interest to the developer. The user will normally just use a simple declaration such as
SortedList<Float> list = new SortedList<Float>();and all will behave as expected. The task of the developer is to ensure good behaviour in the widest range of circumstances in which good behaviour can legitimately be expected.