11.6 Bounded Type Parameters
In the declaration of the generic class Node<E>, the type parameter E is unbounded— that is, it can be any reference type. However, sometimes it may be necessary to restrict what type the type parameter E can represent. The canonical example is restricting that the type parameter E is Comparable<E> so that objects can be compared.
Wildcard types cannot be used in the header of a generic class to restrict the type parameter:
class CmpNode<? extends Comparable> { … } // Compile-time error!
However, the type parameter can be bounded by a constraint as follows:
class CmpNode<E extends Comparable<E>> { // E is bounded.
// …
}
In the constraint <E extends Comparable<E>>, E is bounded and Comparable<E> is the upper bound. This is an example of a recursive type bound. The declaration above states that the actual type parameter when we parameterize the generic class CmpNode must implement the Comparable interface, and that the objects of the actual type parameter must be comparable to each other. This implies that the type, say A, that we can use to parameterize the generic class, must implement the parameterized interface Comparable<A>.
If we base the implementation of the CmpNode class on the generic class Node<E>, we can write the declaration as follows:
class CmpNode<E extends Comparable<E>> extends Node<E> {
// …
}
The extends clause is used in two different ways: for the generic class CmpNode to extend the class Node<E>, and to constrain the type parameter E of the generic class CmpNode to the Comparable<E> interface. Although the type parameter E must implement the interface Comparable<E>, we do not use the keyword implements in a constraint. Neither can we use the super clause to constrain the type parameter of a generic class.
If we want CmpNodes to have a natural ordering based on the natural ordering of their data values, we can declare the generic class CmpNode as shown in Example 11.8:
class CmpNode<E extends Comparable<E>> extends Node<E>
implements Comparable<CmpNode<E>> {
// …
}
Note how the Comparable interface is parameterized in the implements clause. The constraint <E extends Comparable<E>> specifies that the type parameter E is Comparable, and the clause implements Comparable<CmpNode<E>> specifies that the generic class CmpNode is Comparable.
Example 11.8 Implementing the Comparable<E> Interface
class CmpNode<E extends Comparable<E>> extends Node<E>
implements Comparable<CmpNode<E>> {
CmpNode(E data, CmpNode<E> next) { super(data, next); }
@Override public int compareTo(CmpNode<E> node2) {
return this.getData().compareTo(node2.getData());
}
}
Here are some examples of how the generic class CmpNode in Example 11.8 can be parameterized:
CmpNode<Integer> intCmpNode = new CmpNode<>(2020, null); // (1)
CmpNode<Number> numCmpNode = new CmpNode<Number>(2020, null); // (2) Error!
CmpNode<Integer> intCmpNode2 = new CmpNode<>(2021, null);
int result = intCmpNode.compareTo(intCmpNode2);
The actual type parameter Integer at (1) implements Comparable<Integer>, but the actual type parameter Number at (2) is not Comparable. In the declaration CmpNode<A>, the compiler ensures that A implements Comparable<A>.
Leave a Reply