Bounds—Requiring a Type Parameter to Implement an Interface or Extend a Parent Class

You'll get more use out of generic types if there's a way to call additional methods on them, more than just the handful of methods implemented in java.lang.Object. You can call a specific method, such as compareTo(), on a generic type within the body of a generic class, if you can be sure that the type has such a method. There are two ways to ensure that some class has a given method:

  • Make the class a child of some parent that has the method, or

  • Make the class implement an interface that has the method.

In general terms, we want a way to tell a generic that one of the parameter types must implement some interface or extend some parent. We express this with a Java generics feature called bounds. Bounds are limitations or restrictions put on type parameters.

Bounds say “The type that you use as an argument to the generic must extend this class” or “... must implement this interface”. The purpose of bounds is to inform the compiler of methods that are guaranteed to be present in the type argument. Then we can call them, and it is guaranteed not to fail at run-time with a “method not found” exception.

An ordinary generic parameter is written as:

class SomeGenericClass <X>

We want a way to add this kind of condition to X:

<X has-to-extend-or-implement Comparable>

Then we can call Comparable's methods on X, like this:

class SomeGenericClass <X has-to-extend-or-implement Comparable> {
         void check(X x1, X x2) {
               int result = x1.compareTo(x2);

               /* more code, omitted*/

Bounds are restrictions in the sense that not every type will meet the requirements. But they are really permissions in the sense that they grant extra capabilities inside the body of the generic class. They guarantee that objects of the type parameter will have certain methods and therefore you can invoke those methods. I mentioned above that at run-time, an instance of a generic class has no information about the type argument and holds it as an Object. That was a simplification to avoid confusing you too soon. If a generic type parameter has a non-trivial bound, that information is carried through to run-time.

The pseudo-keyword used above “has-to-extend-or-implement” is a bit longer than necessary. The important thing here is guaranteeing the availability of certain methods on X, and the Java compiler doesn't care whether it is done by implementing an interface or by extending a class. So the actual form uses the keyword “extends”. (In the beta compiler, you can't substitute the obvious word “implements” if you are talking about an interface. “Extends” does double duty here for a class and for an interface. Everyone hopes that changes in the final version).

class SomeGenericClass <X extends SomeInterfaceOrClass> {

You could set up a generic class that will only accept subtypes of Thread as a parameter:

class SomeGenericClass <X extends Thread> {

Inside that generic class, you may now invoke Thread methods on X objects:

void makeItGo(X myThr) {
     myThr.start();

That's easy to understand and read. Don't worry, it gets more complicated: What if you need the generic parameter X to extend some other class or interface that is itself generic? What then, eh? What if you need X to extend, not Thread, but Comparable? Comparable takes a generic parameter saying what type something can be compared to. So what we have above, won't quite work for Comparable as written. Keep on reading for the full details!

Generic parameter bounds that mention a generic type

Here's where the syntax starts to get hard to read. If the interface or class that you mention as a bound, is itself generic, then you must give its full signature, including its generic type parameters. For instance:

class SomeGenericClass <X extends Comparable<X> > {

So here it says “X is a type parameter to this class, and X has a bound. X must extend/implement Comparable. And not just any old Comparable, but Comparable of X”. The net effect is that only types which implement Comparable on themselves will be allowed as type arguments to the generic class. That would ensure you could call x.compareTo(x2) within the generic body.

Going back to the Table example, here's how you require that all types, X, used as a Key type parameter must implement the Comparable<X> interface:

class Table <Key extends Comparable<Key>, Value> {

        boolean add(Key k, Value v) { /*more code*/ }

That allows us to call compareTo() on Key values within the generic, so we can tell the ordering of keys and perhaps keep them sorted for efficient access.

Using a concrete example from earlier in the chapter, here's how you would instantiate a Table object with Integer keys, and String values. If you check the declaration of Integer in the Java API, you will see that it does implement Comparable<Integer>. Therefore Integer can be used for the Key parameter in this declaration.

Table<Integer,String> digitStrings = new Table<Integer,String>();

You might set up a table like this so that you can translate easily between int digits and their text equivalent. Some code to do this might be:

String old;
old = digitStrings.put(0,"zero");
old = digitStrings.put(1,"one");
old = digitStrings.put(2,"two");

Notice that we provided an int value as the first argument to the put() method where the declaration called for a Key, and which we instantiated with an Integer.

We don't have to write:

old = digitStrings.put(new Integer(0),"zero");

because autoboxing does that conversion from int to Integer for us automatically.

If you want to try compiling this example, use the name java.util.Hashtable instead of Table. Not everyone is familiar with Hashtables yet, but everyone knows what a table of <key, value> pairs is. A hashtable lets you put or retrieve a value using a key, instead of a position in a list like some of the other collection classes. When you put() something in a Hashtable, the method returns the old value that was in the table under that key, if there was one. That way you can tell if you replaced something already there, or genuinely added something fresh into the table.

Several generic parameter bounds

You might find that some of your generic types need to implement several interfaces. That is expressed in a generic parameter by using the “&” ampersand character:

class Table <Key extends Comparable<Key> & java.io.Serializable, Value> {

You can include up to one bound for a specific parent class (such as Thread, or Employee) in there too. The limit is one in Java, because you have exactly one immediate parent class (no multiple inheritance).

Table 15-1 summarizes in one place these new terms, and examples of what they look like.We have not yet covered the last two items in the table, but they are included here for completeness. The last two items, wildcards and generic methods are not intended for use in generic classes directly. These features are mainly in Java to support your methods in non-generic classes which use a generic type from the library. You will use wildcards and/or generic methods when you write an application method that needs to accept a generic type as an argument. As such, we will cover these two features in the next chapter, which describes generic collections, and how to use them. That's the end of generics for now.

Table 15-1. Summary of terminology

Concept

Example

Generic class

class LinkedList <E> { }

Generic interface

interface Comparable <T> {
               void myMethod(T t) {  }  }

Parameterized type

class LinkedList <E>  { }
// LinkedList is a parameterized type
//  the generic parameter is E

Type parameter

<E>

Type argument

String // (the thing that replaces the type parameter)

Instantiation

LinkedList<String> sll = new LinkedList<String>()

Bounds

<E extends SomeClass & SomeInterface<E> >

Wildcard

LinkedList<?>

Generic method

public static <T> void copy(
                List<? super T> dest,
                List<? extends T> src  )

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.14.251.57