Generic Classes and Methods
A type parameter that can be any type limits you to operations that every type supports.
Why Flexibility Hits a Wall
Generic classes and generic methods are written around a type parameter, usually named T, that stands in for some type chosen later. That flexibility is the whole point: one method can work on integers, strings, or any other type without being rewritten. But flexibility has a cost, and it shows up the moment a generic method needs to compare two of its inputs. You would expect the same code to work for every type that supports comparison. Yet the techniques covered so far do not let you write that method. The reason is worth understanding before any new tool is introduced.
What do you think happens?
Can the body of a generic method call compareTo() on a value of type T when T has no restriction at all?
Reveal answer
No, compareTo() is not guaranteed for an unrestricted T
compareTo() is defined in the Comparable interface and is not defined for all objects. An unrestricted type parameter therefore cannot rely on it, because it may stand for a type that has no such method.
What an Unrestricted Type Parameter Can Use
A type parameter with no restriction is a promise that the code works for any type. The only safe assumption the code can make about a value of that type is that it supports the operations every type supports. Any operation that belongs only to some types is outside that promise, and the code may not use it. The source describes this directly: a type parameter that can be any type limits you to operations that every type supports.
The two columns in the diagram are two different contracts with the code. In the unrestricted column, the code may only rely on what every possible type supports, so the every-type operations are available but compareTo() is not. In the restricted column, the code may also rely on the operations that belong to Comparable, which is the only route to compareTo() that the source describes.
Tracing a Generic Method That Compares
Suppose you want a generic method that returns the larger of two values. The natural first attempt uses compareTo() on the first argument. Read the code below and trace what happens to the value a when the method runs. The variable a has the type T, and T could be any type, so the compiler has no guarantee that a has a compareTo() method.
The correct comparison logic is the same. Only the declaration of T has to change, so that the body is allowed to call compareTo(). The next section shows how that declaration works.
Bounding T with Comparable
Restricting a type parameter to Comparable tells the compiler that the generic code applies only to comparable objects. The restriction is written as a bound on the type parameter, for example <T extends Comparable<T>>. Once that bound is in place, the body of the generic method or class is free to call compareTo(), because every type that the method can be used with is known to provide it.
A bounded type parameter is a type parameter that is restricted to types meeting a stated condition. Here, T extends Comparable<T> restricts T to types that the Comparable interface describes, which makes the methods of that interface, including compareTo(), usable on values of type T.
Tracing the Restricted Version
public static <T> T largest(T a, T b) {
if (a.compareTo(b) >= 0) {
return a;
}
return b;
}T is unrestricted, so compareTo() is not guaranteed on a
public static <T extends Comparable<T>> T largest(T a, T b) {
if (a.compareTo(b) >= 0) {
return a;
}
return b;
}T extends Comparable<T>, so compareTo() is guaranteed on a
Trace the restricted version step by step. The method receives a and b, both of type T. Because T is bounded by Comparable, the call a.compareTo(b) is valid for any types the method can be used with. The result is zero or positive when a is not smaller than b, so the method returns a. Otherwise it returns b. Only the declaration changed, and the body is the same as the rejected version.
pear
7- The String and Integer values both satisfy the Comparable bound
largest(3, 7)returns 7 because 3 is smaller than 7largest("apple", "pear")returns pear because pear sorts after apple
The Cost of Restriction
Restriction is a trade. The type parameter now covers fewer types, which is a loss of generality. In return, the code gains operations that are guaranteed by the restriction, and compareTo() is the central example. A method that was usable with any type becomes usable only with types that are comparable.
Mistakes Beginners Make
Common mistake Assuming compareTo() works on any T
What it looks likepublic static <T> T largest(T a, T b) { if (a.compareTo(b) >= 0) { return a; } return b; }Why it failscompareTo()is defined in the Comparable interface and is not defined for all objects, so an unrestricted type parameter cannot rely on it.Do this insteadDeclare the bound, as in <T extends Comparable<T>>, so the body can call compareTo().
Common mistake Treating the bound as free
Why it failsA bound narrows the type parameter to fewer types, so the method can no longer be used with every type it once accepted.
Do this insteadAdd only the restriction the body needs, and check which types callers can still pass.
Common mistake Forgetting that the bound belongs in the declaration of T rather than in the method body
Predicting the Restriction in Practice
A generic method sorts nothing and only needs to decide whether one value of type T is smaller than another. Write the declaration of the type parameter that allows the body to call compareTo(), and explain in one sentence what the bound gives you and what it costs you.
Hints
- Look at which interface defines
compareTo() - The bound should be written on T in the method declaration
- The cost is that T now covers fewer types
Key Takeaways
- A type parameter that can be any type limits code to operations that every type supports
compareTo()is defined in the Comparable interface and is not defined for all objects, so an unrestricted T cannot rely on it- Restricting T to Comparable, as in <T extends Comparable<T>>, makes
compareTo()usable inside the generic code - Restriction is a trade: the code gains guaranteed operations, but the type parameter covers fewer types