Skip to main content

Java Generics III - Bounds and Wildcards

Posted by hellofadude on July 10, 2014 at 5:42 AM PDT

In part two of this series, I demonstrated the effect of erasure on type parameters within the body of a class. I used examples to show how type parameters were limited to accessing methods of the root Object class irrespective of the actual type with which it was replaced at a later time. I also demonstrated how to specify a bound with which one may constrain the type parameter so as to have access to methods other than those available to Object within the body of a class.

Here is another example code to emphasize the point; in this instance we have an oversimplified parameterised Converter class, whose type parameter is constrained to the Currency class and it's subtypes. The Converter class has only one method - convert() - that checks the equivalence of the name of the type parameter as returned by the getCurrency() method in another class, and returns a simple string on that basis. Here is the code:-

package generics.java.erasure;

abstract class Currency {
     public String getCurrency() { return this.getClass().getSimpleName(); }
}
class Dollar extends Currency { }

public class Converter<T extends Currency> {
     private T type;
     static int GBP;
     public Converter(T aType) { type = aType; }
     public String convert(int amt) {
         if(type.getCurrency().equals("Dollar"))
             return "Value of $" + amt + " is " + "£" + amt * 0.60;
         return ("Error! Dollar required: found" + type.getCurrency());
     }
     public static void main(String[] args) {
         Converter<Dollar> converter = new Converter<Dollar>(new Dollar());
         System.out.println(converter.convert(100));
     }
}
/* Output
Value of $100 is £60.0
*//

What I am trying to get across with the above example is that by specifying an upper bound in the form <T extends Currency>, we constrain the type parameter to the Currency class and it's subtypes making it possible to access methods of this class within the body of our generic class.

A generic type parameter may have more than one bound that may include classes and interfaces and as long as the bounded class conforms to the specified interface, it can be used with the configured generic. Here is a more elaborate example where the base class Genre<T> acts as a container whose functionality is propagated down the inheritance hierarchy:-

package generics.java.bounds;

interface Adventure {
     String adventure();
}
interface Action {
     String action();
}
class Story {
     String plot(){ return "Plot"; }
}
class Genre<T> {
     T anItem;
     Genre(T item) { anItem = item; }
     T getItem() { return anItem; }
}
class Fantasy<T extends Adventure> extends Genre<T> {
     Fantasy(T item) {
         super(item);
     }
     String adventure() {  return anItem.adventure();    }
}
class MythicalFiction <T extends  Action & Adventure> extends Fantasy<T> {
     MythicalFiction(T item) {
             super(item);
     }
     String action() {
         return anItem.action();
     }
}

class Bounded extends Story implements Action, Adventure {
     public String action() {
         return "Action";
     }
     public String adventure() {
         return "Adventure";
     }
}
public class BoundedGenerics <T extends Story & Action & Adventure> extends MythicalFiction<T> {
     public BoundedGenerics(T item){
         super(item);
     }
     String plot(){ return anItem.plot(); }
     public static void main(String[] args) {
         BoundedGenerics<Bounded> container = new BoundedGenerics<Bounded>(new Bounded());
         System.out.println(container.action());
         System.out.println(container.adventure());
         System.out.println(container.plot());
     }
}
/* Output
Action
Adventure
Plot
*//

The classes in this example are used to extend an inheritance hierarchy by adding bounds to each level. As you might observe the MythicalFiction class has two interface bounds while the BoundedGenerics class has three bounds made up of a class and two interfaces. Notice that in this arrangement it is important to declare the class first before the interfaces or you will get a compiler error. Because of these bounds we are able to use delegation to call methods implemented by the Bounded class in the case of interfaces, in addition to using the type variable to call methods in the inherited class directly.

Additionally, I find it a useful mental practice to occasionally make a constant comparison between generics and arrays being that an array is also a container of sorts and the one is partly motivated by the perceived limitations of the other.
One interesting comparison is how both types deal with upcasting. While arrays are able to perform upcasts fairly easily, they have a peculiar behaviour where they subsequently fail to discriminate between the types of objects placed into the container, making you have to wait until runtime to report any errors that only arise when you try to retrieve an unexpected type from the container. Here is an example of this phenomenon:-

package generics.java;

import java.util.Arrays;

class Building {}
class Library extends Building {}
class PrisonLibrary extends Library {}

public class ArrayUpcasting {
     public static void main(String[] args) {
         Building[] building = new Library[6];     // Upcast - ok
         for(int i = 0; i < 6; i++)
             building[i] = new Library();
         for(int j = 0; j < 2; j++)
             building[j] = new PrisonLibrary();    // Derived class
         for(int z = 0; z < 2; z++) {
             //! building[z] = new Building();        RuntimeException - ArrayStoreException
         }
         System.out.println(Arrays.toString(building));
     }
}

As you can see, it is no big deal to upcast a Library array to a Building array. The interesting bit comes in the final loop, when we add an object that is not a subtype of Library class. Even more interesting is the fact that we do not get to know about this until the final statement when we try to print the array using the static Arrays.toString() method and then get a RuntimeException.
This somewhat benign limitation can be a potential source of bugs and partly inspired the need for generics in Java which are statically typed objects. Static type checking refers to the ability of the compiler to ensure only the correct type can be added to a generic container. This capability however comes with a tradeoff that means undertaking an upcast with generics is not quite so straightforward. For instance if we tried to perform an upcast with generics as we just did with arrays, we get the following result:-

package generics.java;

import java.util.*;

public class NoUpcastGenerics {
      //List<Building> container = new ArrayList<library>(); Error - Type mismatch
}

Here we get a compiler error telling us that a List<Building> is not the same as an Array<library>, which may seem strange at first, until you remember that generics are not first class objects i.e. they were not made a part of Java from the start and consequently do not have built-in covariance. Covariance is a term that is used to characterise the relationship between a family of types.

In order to be able to perform an upcast with generics you would again need to assist the compiler by constraining the actual type to a particular bound. But as you will note, this presents another problem:-

package generics.java;

import java.util.*;

class Office extends Building {}

public class UpcastedGenerics {
    public static void main(String[] args) {
         List<? extends Building> container = new ArrayList<library>();// ok
         //! container.add(new Building());           Error ..Not applicable
         //! container.add(new Library());              Error ..Not applicable
         //! container.add(new PrisonLibrary());  Error ..Not applicable
         //! container.add(new Office());                Error ..Not applicable
     }
}

The question mark signifies a wildcard and in this instance List<? extends Building> is a wildcard subtype that constrains the actual type to subtypes of the Building class which of course includes the Library class. With this technique, it is now possible to perform the upcast, but the compiler will not allow you to add anything into the container because it has no way of verifying the particular subtype. The wildcard properly means any type of Building and because the add() method calls for a type argument which the compiler is unable to verify precisely because it has no way of doing so, it refuses all calls to the method. The result would be exactly the same even if you do not use an 'upcast'. Because the compiler is unable to verify what might replace the wildcard character, it will not call add() for any object even the specified bound.

You can get around this by using the supertype wildcard to safely 'write' to a Collection. Supertype bounds use the keyword super as opposed to extends keyword and are used to indicate that the wildcard is bounded by any base class of a particular class. Here is an example:-

package generics.java;

import java.util.ArrayList;
import java.util.List;

public class SuperTypeWildcards {
     public static void main(String[] args) {
         List<? super Library> container = new ArrayList<Library>();
          container.add(new Library());
          container.add(new PrisonLibrary());
          //! container.add(new Building());       // Error: ..Not applicable
          //! container.add(new Office());           // Error: ..Not applicable
        
     }
}

As you can see, we can now add objects of that type or subtype into the list. Here is an example that demonstrates how to use a type parameter with supertype and subtype bounds as a way to write to and read from a Collection respectively. Note that type parameters only work within the formalised structures of a method or class:-

package generics.java;

import java.util.ArrayList;
import java.util.List;

public class GenericReaderWriter<T> {

     static <T> void wildCardWrite(List<? super T> list, T item) {
         list.add(item);
     }

     T  wildCardRead(List<? extends T> list) {
         return list.get(0);
     }
     public static void main(String[] args) {
         List<Building> buildings = new ArrayList<Building>();
         wildCardWrite(buildings, new Building());
         wildCardWrite(buildings, new Library()); // can add a Library to building list
         for(Building aBuilding : buildings)
             System.out.println("Added: " + aBuilding.getClass().getSimpleName());
         GenericReaderWriter<Building> reader = new GenericReaderWriter<Building>();
         for(int i = 0; i < buildings.size(); i++)
             //References of the base type
             System.out.println("Retrieved: " + reader.wildCardRead(buildings).getClass().getSimpleName());
    }
}
/Output
Added: Building
Added: Library
Retrieved: Building
Retrieved: Building
*//

This class has two methods, one to write to a Collection using a wildcard supertype and one to read from a Collection using a wildcard subtype. Note that the wildCardRead() methods returns base class references. To return exact types, use an index.

Other issues

There are a number of other peculiarities with which one ought to become thoroughly familiar when working with generics. For instance casts have no effect when using type parameters, so much so that casting an Object to type T when returning from a method as in the get() method in the following example has no effect, because the actual type is already erased to it's default object:-

package generics.java;

class Stringer<T> {
     private Object[] container = new Object[20];
     private int index = 0;
     void set(T atype) {
         container[index++] = atype;
     }
     @SuppressWarnings("unchecked")
     T get(){
         return  (T)container[--index]; // dummy cast
     }
}
public class DummyCasts {
     public static void main(String[] args) {
         Stringer<Integer> list = new Stringer<Integer>();
         Integer[] iterator = new Integer[10];
         for(int i = 0; i < 10; i++) {
             list.set(new Integer(i));
             iterator[i] = i;
         }
         for(Integer i : iterator)
             System.out.print(list.get() + " ");
     }
}
/* Output
9 8 7 6 5 4 3 2 1 0
*//

Effectively the get() method is casting an Object to an Object, which seems rather superfluous, however without the cast you will get a compiler error reporting a type mismatch - "cannot convert from object to T". In these circumstances, it helps to remember that the compiler undertakes type checking and casting at the boundaries i.e the points in your program where objects enter and leave a method.

Further, because of erasure you have to take extra care when overloading a method using generics. For instance the following code will not compile because both arguments will be erased to their raw List type, and because they have the same name the compiler will see them as having the same signature:-

package generics.java;

import java.util.List;

public class OverloadedSignature <T, F> {

     // void f(List<T> alist) {} // Compiler error
     // void f(List<F> alist) {} // Compiler error
}

Also, even though you can not inherit from a generic parameter directly, you can inherit from a class that has a generic parameter in it's own definition and pass your derived class as its parameter type in an arrangement that forces the class to produce a base class that uses your derived class for it's arguments and return type. Here is an example:-

package generics.java;

class TypeHolder<T> {
     T element;
     void setter(T anElement) {
         element = anElement;
     }
     T getter() { return element; }
     void print() {
         System.out.println("Type: " + element.getClass().getSimpleName());
     }
}

class OneType {}
class SecondType extends TypeHolder {}

public class GenericInheritance extends TypeHolder<GenericInheritance>{
     public static void main(String[] args) {
        GenericInheritance gI = new GenericInheritance(), gI2 = new GenericInheritance();
        gI.setter(gI2);
        gI.print();
        SecondType sT = new SecondType(), sTI = new SecondType();
        sT.setter(new OneType());
        OneType oT = sT.getter();
        sT.print();
     }
}
/* Output
Type: GenericInheritance
Type: OneType
*//

You are not limited to using just the derived type as a generic type parameter, you can use any class you wish as demonstrated by the SecondType class. The result in either case is the same, effectively, the base class, TypeHolder, becomes a template for common functionality for all it's derived classes, except that this functionality will use the configured type parameter for all it's arguments and return values.

You can go further by using self bounded types to force the generic class to be used as it's own bound argument like so:-

package generics.java;

public class SelfBoundedType<T extends SelfBoundedType<T>> {}

This has the curious effect of an infinite reflection the type of which you get when you stand between two mirrors that face each other from opposite sides. The value of self bounded types lies in the fact that they can be used to produce covariant return and argument types i.e. method arguments that vary according to their subclasses.
To use a self bounded class, you must inherit from it and pass the derived class as the type parameter, or another derived class that uses the parameter of another self bounded type:-

package generics.java;

class SelfBounded<T extends SelfBounded<T>> {
     void set(T arg) { System.out.println(arg.getClass().getSimpleName()); }
     @SuppressWarnings("unchecked")
     T get() { return (T)this; }
}

class Base extends SelfBounded<Base> {}
class Derived extends Base {}

public class SelfBoundedTypes {
     public static void main(String[] args) {
         Base boundedBase = new Base();
         boundedBase.set(new Base());
         System.out.println(boundedBase.get().getClass().getSimpleName());
         Derived derivedBase = new Derived();
         derivedBase.set(new Derived());
         boundedBase.set(new Derived());
         //boundedBase.set(new SelfBounded()); Error SelfBounded
         // can not be resolved to a variable
     }
}
/* Output
Base
Base
Derived
Derived
*//

The type arguments to the set() and get() methods in the SelfBounded class are overridden in both subclasses to become the exact type of the subtype. As you can see, the compiler rejects the attempt to pass the base type argument because the argument does not exist in the subclass. In non-generic code, it would not be possible to vary argument types in a way that would allow you to override the base class argument. This technique allows you to place functionality within your base class that you want to vary according to your derived classes.
You can also use self-bounding for generic methods to prevent them from being applied to anything that does not conform to a self bounded argument.

Exceptions

Generics do not work with exceptions because the effects of erasure make it impossible for the catch clause to catch a generic type. However, you can use type parameters with the throws clause to vary the type of thrown checked exceptions:-

package generics.java;

import java.util.ArrayList;

interface Game<E extends Exception>{
     void start() throws E;
}
class App<T extends Exception> extends ArrayList<Game<T>> {
      void selectGame() throws T {
         for(Game<T> game : this)
              game.start();
         }
}
class GameOneException extends Exception {
     public GameOneException(String message) {
         super(message);
     }
}
class GameOne implements Game<GameOneException> {
     boolean ready = false;
     public void start() throws GameOneException {
         if(ready == true)
             System.out.println("Starting Game One");
         throw new GameOneException("Game one not ready!");
     }
}
class GameTwoException extends Exception {
     public GameTwoException(String message) {
         super(message);
     }
}
class GameTwo implements Game<GameTwoException> {
     Integer count;
     public GameTwo() {}
     public GameTwo(int lives) {
         count = lives;
     }
     public void start() throws GameTwoException {
         if(!count == null)
             System.out.println("Starting Game Two");
         throw new GameTwoException("Game Two.. No lives left!");
     }
}
public class GenericException {
     public static void main(String[] args) {
         App<GameOneException> appOne = new App<GameOneException>();
         App<GameTwoException> appTwo = new App<GameTwoException>();
         appOne.add(new GameOne());
         appTwo.add(new GameTwo());
         try {
             appOne.selectGame();
         }catch (GameOneException anException) {
             System.out.println(anException);
         }
         try {
             appTwo.selectGame();
         } catch(GameTwoException anException) {
             System.out.println(anException);
         }
     }
}
/* Output
generics.java.GameOneException: Game one not ready!
generics.java.GameTwoException: Game Two.. No lives left!
*//

In this example, the Game interface defines a type parameter bounded by the Exception class, which is thrown in it's start() method. When the Game interface is implemented, each game substitutes the type parameter with an actual type that is a subtype of Exception. The App class is able to vary the exception precisely because of the generic parameter associated with the throws clause.