When specifying a type parameter, you can create an upper bound that declares the superclass from which all type arguments must be derived. This is accomplished through the use of an extends clause when specifying the type parameter, as shown here:
<T extends superclass>
This specifies that T can only be replaced by superclass, or subclasses of superclass. Thus, superclass defines an inclusive, upper limit. You can use an upper bound to fix the NumericFns class shown earlier by specifying Number as an upper bound, as shown here:
// In this version of NumericFns, the type argument for T must be either Number, or a class derived from Number.
class NumericFns<T extends Number> {
T num;
// Pass the constructor a reference to a numeric object.
NumericFns(T n) {
num = n;
}
// Return the reciprocal.
double reciprocal() {
return 1 / num.doubleValue();
}
//Return the fractional component.
double fraction() {
return num.doubleValue() - num.intValue();
}
// ...
}
//Demonstrate NumericFns.class BoundsDemo {
public static void main(String args[]) {
NumericFns<Integer> iOb =new NumericFns<Integer>(5);
System.out.println("Reciprocal of iOb is " + iOb.reciprocal());
System.out.println("Fractional component of iOb is " + iOb.fraction());
System.out.println();
NumericFns<Double> dOb =new NumericFns<Double>(5.25);
System.out.println("Reciprocal of dOb is " +
dOb.reciprocal());
System.out.println("Fractional component of dOb is " +
dOb.fraction());
// This won't compile because String is not a subclass of Number.
NumericFns<String> strOb = new NumericFns<String>("Error");}
}
The output is shown here:
Reciprocal of iOb is 0.2
Fractional component of iOb is 0.0
Reciprocal of dOb is 0.19047619047619047
Fractional component of dOb is 0.25
Notice how NumericFns is now declared by this line:
class NumericFns<T extends Number> {
Because the type T is now bounded by Number, the Java compiler knows that all objects of type T can call doubleValue( ) because it is a method declared by Number. This is, by itself,
a major advantage. However, as an added bonus, the bounding of T also prevents nonnumeric NumericFns objects from being created. For example, if you try removing the comments from
the lines at the end of the program, and then try re-compiling, you will receive compile-time errors because String is not a subclass of Number.
Bounded types are especially useful when you need to ensure that one type parameter is compatible with another. For example, consider the following class called Pair, which stores
two objects that must be compatible with each other:
class Pair<T, V extends T> {
T first;
V second;
Pair(T a, V b) {
first = a;
second = b;
}
// ...
}
Notice that Pair uses two type parameters, T and V, and that V extends T. This means that V will either be the same as T or a subclass of T. This ensures that the two arguments to
Pair’s constructor will either be objects of the same type or of related types. For example, the following constructions are valid:
// This is OK because both T and V are Integer.
Pair<Integer, Integer> x = new Pair<Integer, Integer>(1, 2);
// This is OK because Integer is a subclass of Number.
Pair<Number, Integer> y = new Pair<Number, Integer>(10.4, 12);
However, the following is invalid:
// This causes an error because String is not a subclass of Number
Pair<Number, String> z = new Pair<Number, String>(10.4, "12");
In this case, String is not a subclass of Number, which violates the bound specified by Pair.