Generic Interfaces

Interfaces, as well as classes, can be parameterized with generic types. As you'd expect, it looks exactly the same as when a class is parameterized. We'll walk through a complete “before and after” example here, because we'll use it in the next section as part of a more complicated generic parameter declaration.

This example uses the standard java.lang.Comparable interface type that defines a compareTo() method used for comparing two objects. The method returns an int which is negative, zero, or positive to indicate this is smaller, equal, or bigger than the argument.

java.lang.Comparable without generics

The Comparable interface looks like this in JDK 1.4:

public interface Comparable {
    int compareTo(Object obj)
}

A class that implements the interface would be declared like this in JDK 1.4:

public class Timestamp2 implements java.lang.Comparable {
    int hrs;
    int mins;
    int secs;

    public int compareTo(Object t) {
        Timestamp2 t2 = (Timestamp2) t;
        return (this.hrs - t2.hrs);
    }

The subtraction in the compareTo method is a cheap, fast way of getting the necessary negative, zero, or positive result. As long as the subtraction never overflows, it will give an accurate answer. To keep things simple, we'll define two timestamps as equal if their hour component matches.

When a class implements the Comparable interface, you can compare two of its objects like this:

int result = myTimestamp.compareTo(yourTimestamp);

Adding generics to java.lang.Comparable

image

In JDK 1.5, the Comparable interface now looks something like this:

public interface Comparable<T> {
    int compareTo(T thing)
}

That's the same notation for a type parameter that we saw earlier with classes. A class can implement the interface like this:

public class Timestamp implements java.lang.Comparable<Timestamp>{
        int hrs;
        int mins;
        int secs;

    public int compareTo(Timestamp t) {
        return (this.hrs - t.hrs);
    }

We thus provide the arguments to the generic parameters in the definition of the implementing class, rather than in the object instantiation. The compiler knows that class Timestamp is implementing the Comparable interface on Timestamps, not Objects. So method compareTo can take a Timestamp parameter, not an Object, and explicit casting is not needed inside the method. The compiler still needs to insert it in the generated code.

Method calls look the same as if you didn't use generics:

int result = myTimestamp.compareTo(yourTimestamp);

The most common case is comparing two objects of the same class. If a class wants to allow its objects to be compared to objects of some other class, you can also express that using the generic parameter. One example might be allowing Timestamps to be compared to Strings. They are equal if the String contains the text representation of the Timestamp, e.g. “noon” and 12:00:00. Here's how you would write that:

public class Timestamp implements java.lang.Comparable<String>{
        int hrs;
        int mins;
        int secs;

    public int compareTo(String s) { /*more code here*/ }

Restrictions on a generic type

As designed for Java, genericity is a compile time feature. Practically nothing about the actual type parameters persists through to run-time. There is just one class file for a generic class, no matter how many times the generic class is instantiated. At run-time, an instance of a generic class has no information about what type argument was actually given to some instance as its type parameter at compile time. (There's some structural information in the class file, available through reflection). It may actually be a String, but the generic class holds it as an Object. That's why the compiler still needs to insert casts.

This run-time amnesia has been termed type erasure. There are three limitations on the use of generic parameters, some of which follow from type erasure:

  1. You cannot use a type parameter to create a new instance:

    public class Table <Key, Value> {
       Value v = new Value();  //  illegal!
    }

    Creating an instance out of a type parameter makes little sense. The actual type parameter might be an interface or a class with private constructors.

  2. You should not use a type parameter in a cast. The compiler cannot check the validity of the cast. You're opening up the possibility generics try to avoid—a run-time class cast exception:

    Key k2 = (Key) someObj;  //  causes compiler warning!

    If you do write this code, the compiler will issue a warning, and suggest that you recompile using the “-Xlint” option to get a list of the specific lines it is warning you about. That option should have been made the default for every Java compilation.

  3. All reference types have the methods defined in the ultimate parent class, Object. So it is legal to call methods of Object, like toString() on any generic parameter:

    public class Table <Key, Value> {
        boolean add(Key k, Value v) {
           String s = v.toString();  //  legal
        }
    }

There's no fundamental theoretical requirement for type erasure. It was one design among several possibilities, chosen because it did not require any changes to the JVM. C# made a different design choice, keeps the information around, and does the parameter substitution at run-time including compiling each instantiation of a new type on-the-fly! It has a slightly richer choice of operations because of this, along with a more complicated compiler/run-time and larger run-time performance penalty (while the code is compiled).

C++ does something else again. The designer of C#, Anders Hejlsberg, summarized the different approaches as “C# generics are really just like classes, except they have a type parameter. C++ templates are really just like macros, except they look like classes”. C++ does the instantiation of a new type at compile or link time, but it doesn't check that a generic type actually has the methods you invoke on it. So those operations can fail with a cryptic message at run-time. That won't happen in Java.

You may need to call more methods on a type parameter, than just those in Object. You can arrange this in the usual way - tell the compiler that the actual type argument has to implement some interface or extend some class. The way to do that is explained in the next section.

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

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