Saturday, March 23, 2013

Declaring Java Generic Classes and Methods


The declaration of a generic class, constructor, interface or method requires a type parameter. Where a type variable is between angle brackets (<>) in the declaration of a generic class, constructor, interface or method, it is called the type parameter or formal type parameter1.

The Java Programming Language
This is a basic generic class declaration:

public class MyClass<T> {}

where <T> is the type parameter.

This is a basic generic constructor declaration:

public <T> MyOtherClass() {}

where <T> is the type parameter.

This is a basic generic method declaration:

public <T> void MyMethod(T a) {}

where <T> is the type parameter.

Type variables are placeholders for an actual type. All type variables must receive a value from a type parameter that has been passed a type argument. A class may have a generic method or generic constructor, but it is not sufficient to classify it as a generic class. A method may have type variables in its signature or body, but it is not sufficient to classify it as a generic method.

The class below contains a generic method, but the class itself is not generic.

public class MyClass {

    public <T> void MyMethod(T a) {/*...*/}
}

The methods in the generic class below use type variable T, but they are not generic methods.

public class SomeClass<T> {
    T a;

    public void setA(T a) {
        this.a = a;
    }

    public T getA {
        return a;
    }
}

The class below contains a generic constructor, but the class itself is not generic.

public class MyClass {

    public <T> MyClass() {/*...*/}
}

The type parameters of a generic constructor can only pass values to type variables in the body of the constructor.

A generic class will pass values to type variables within the signature or body of a generic constructor without the constructor being declared generic. The class below shows an example of a generic class with a non-generic constructor.

public class MyClass<T> {
    T a;

    public MyClass(T a) { this.a = a; }

    public void MyMethod(T a) {/*...*/}
}

Multiple type parameters may be used for the same class, constructor, interface or method.

This is an example of an interface with two type parameters and two method definitions:

public interface MyInterface<T, V> {

    public V MyMethod(T a);

    public void MyMethod2(T a, V b);
}

where V is the type variable for the return type of MyMethod and is also the type variable for the formal parameter b in MyMethod2.

In a class declaration, type variables in angle brackets on the right side of an extended class or interface are type parameters because they are in angle brackets. The variables in these parameters must be the same variable(s) as the subclass's type parameter(s) because they cannot receive type arguments independently outside of the class declaration. When extending a generic class or implementing a generic interface, you may do so by providing them with either type variables that are the same as the subclass's type parameters or providing them concrete type arguments directly.

Below is an example of extending a class and implementing a generic interface with a generic subclass of a single type parameter. In this case we use a type variable <T> for the superclass MyClass and interface MyInterface where T is the same as the subclass's type variable T.

public class SomeClass<T> extends MyClass<T> implements MyInterface<T> {

    public void MyMethod(T a) {/*...*/}
}

Below is an example of extending a non-generic class the same way as was done above. In this case there is no type parameter and therefore the type parameters for the superclass and interface are invalid and the code won’t compile. The only way this will work is to provide the superclass and interface with concrete type arguments.

public class RegularClass extends MyClass<T> implements MyInterface<T> { // Error

    public void MyMethod(Double a) {/*...*/}
}

Below is an example of extending a generic superclass and a generic interface by providing them with concrete type arguments.

public class SomeClass<T> extends MyClass<String> implements MyInterface<Integer> {

    public void MyMethod(T a) {/*...*/}
}

Concrete type arguments must be a reference type (class, interface or array types). Primitives cannot be used as type arguments, but reference types used to box primitives can be used as type arguments. For example, an int cannot be a type argument, but an Integer can.

A type variable can be named any valid Java identifier, but by convention single uppercase letters are always used. A type parameter may be named the same as a class or interface, but the type parameter will be used as a type variable and not the reference type it names.

Below is an example of a generic method whose type parameter’s name is the same as the concrete reference type Integer.

public class SomeClass<Integer> { // Bad type variable name
    private Integer n;

    public void setN(Integer n) {

        this.n = n;
    }

    public Integer getN() {

        return n;
    }
}

If someone read the code above, they would likely mistake the type of field n as being Integer. Since the class's type parameter is Integer; Integer is also the type variable. Therefore SomeClass will still allow any valid type argument for its type parameter and n’s type will be whatever that type is. This is a poor convention and the single uppercase letter convention should always be used to avoid this sort of ambiguity.

Type parameters can be written with one or more bounds, except only one class can be used in the bounds while multiple interfaces can be used along with it. If there is a class type used in multiple bounds, it must always occur first in the list of bounds. Type parameter bounds restrict the type arguments that can be passed to a class or method and only allows subtypes for compatibility.

The following shows an example of a generic class declared with multiple bounds, where ClassA is a class and InterfaceB and InterfaceC are interfaces.

public class SomeClass<T extends ClassA & IntefaceB & InterfaceC> {
    private T n;

    public void setN(T n) {

        this.n = n;
    }

    public T getN() {

        return n;
    }
}

Note that if we extend the above class with generic classes or interfaces and provide a type parameter, we only need to provide T and not the bounds.

Below is an example of a generic method with type bounds.

public class MyClass {

    public <T extends ClassA & InterfaceA> void MyMethod(T a) {/*...*/}
}

One of the reasons to use type bounds is to enable calling methods of an unknown type. For example, if we have an animal farm simulator with a utility class that contains a pokeAnimal method, we would want to create a generic method that will take different types of farm animals.

The following is a naive attempt to do this.

public class Utility {

    public static <T> void pokeAnimal(T animal) {
       animal.makeSound(); // error--cannot find symbol
    }
}

There is an error—the compiler cannot find the makeSound() method because the variable reference animal is of an unknown type T. The reason is, because we can supply an argument of any type to pokeAnimal() and there is no guarantee that makeSound() exists in the type we supply. To remedy this problem, we create a FarmAnimal interface that declares an abstract makeSound() method. This restricts the types we supply to only those that implement the FarmAnimal interface—which guarantees that makeSound() exists.

Below is our modified utility class.

public class Utility {

    public static <T extends FarmAnimal> void pokeAnimal(T animal) {
       animal.makeSound();
    }
}

Type variables can also be used as bounds.

Below is an example of using a type variable as a bound.

public class Utility<B> {

    public static <T extends B> void doSomething(T e) {/*...*/}
}

Note that we must declare a type parameter for the Utility class and supply a type bound argument for <B> when instantiating the class.

This concludes Declaring Java Generic Classes and Methods. The next installment of this series will be about using generic classes and methods. I will cover declaring generic type variables, assigning generic types, instantiating generic classes and invoking generic methods.

1. In a class declaration, type variables in angle brackets on the right side of an extended class or interface are also type parameters, but they must be the same variable(s) as the subclass's type parameter(s) because they cannot receive type arguments independently outside of the class declaration.

1 comment: