Java's Generic Type Parameter


Edit: A revision to correct the inaccuracies is scheduled for a near point in the future.

Type Parameter is another level of abstraction that allows for dynamic variable type handling. It has a few advantages that I'm still too inexperienced to list, but basically, the two big things are support for code-reuse and strong type-checking at compile time. We'll explore the first advantage in this post.

Consider the following two methods:


public static int compare(Ball b1, Ball b2) {
    // compares the sizes of two Ball objects and return a positive number if
    // b1>b2, 0 if equal and a negative number otherwise
    return b1.getRadius() - b2.getRadius();
}

public static int compare(GameConsole ps4, GameConsole xbox1) {
    // similarly with a different metric
    double ps4Val = ps4.getCPUScore()/ps4.getPrice();
    double xbox1Val = xbox1.getCPUScore()/xbox1.getPrice();
    return  ps4Val - xbox1Val;
}

There are two things to notice here.
First, even though we wrote two completely different methods that do two different routines, they essentially achieve the same goal. Is there a way we can generalize this compare() routine to make it work with all sorts of thing so that we don't have to write a billion methods for a billion different things out there? The answer is Generic Type Parameter. Consider the following method declaration:

public static <T> int compare(T o1, T o2) {}

We have abstracted away the explicit type of the things we're comparing. When a user asks us to compare two Integers, we'll compare two Integers; when they ask us to compare two Hondas, we'll compare two Hondas. The second thing to notice (that was mentioned earlier) is that since the metric for comparison of different types of thing are different, we thus rely on the user to supply us with that functionality.
The code thus looks like:

public static <T> int compare(T o1, T o2) {
    return o1.compareTo(o2);
}

Note that compareTo() is a method in the instance o1 (as well as o2 since they are the same type of object). We rely on the user implementing this method (and correctly). Unfortunately, we can only ensure the prior assumption. By restricting the argument types that can be passed into our methods, we can effectively force the user to properly implement (or at least apparently purport to implement) the thing we rely on.

In this case, we need the arguments to posses a compareTo() method, it is thus sensible to accept only Comparable objects as arguments.

public static <T extends Comparable<T>> int compare(T o1, T o2) {
    return o1.compareTo(o2);
}

The interface (or properly termed, in this context, the supertype) Comparable is called the "Upper Bound" of the Type Parameter T. Everything that implements the interface Comparable is thus acceptable in our code (as the interface acts as a contract that forces the object to have the compareTo() method that we rely on).


Put it together, we can have something like this:

// returns 1 if thing1 > 2, 0 if equal and -1 otherwise
 public static <T extends Comparable<T>> int compare(T thing1, T thing2) {
  return thing1.compareTo(thing2);
 }
 
 public interface MyShape extends Comparable<MyShape> {
  public double getArea();
  public int compareTo(MyShape t);
 }
 
 public class Triangle implements MyShape {
  int side1;
  int side2;
  int angle;
  public Triangle() {
   this(10, 7, 45);
  }
  public Triangle(int s1, int s2, int a) {
   side1 = s1; side2 = s2; angle = a;
  }
  public double getArea() {
   return 0.5*side1*side2*Math.sin(angle);
  }
  
  public int compareTo(MyShape o) {
   double area1 = getArea(); double area2 = o.getArea();
   return (area1 == area2) ? (0) : ((area1 > area2) ? (1) : (-1));
  }
 }
 
 public static void main(String[] args) {
  GenericUtility gu = new GenericUtility();
  MyShape t1 = gu.new Triangle(10,20,39);
  MyShape t2 = gu.new Triangle();
  System.out.println(GenericUtility.compare(t1, t2));
 }

It is worth noting the following few things:
  • MyShape is a Comparable object (strictly speaking, an interface), so all Triangle appropriately inherits the "comparability", however:
  • Triangles themselves do not implement Comparable, so their "inferred typename" by the Java compiler (in this case GenericUtility.Triangle) does not satisfy the condition
    T extends Comparable<T>
    . We have to pass in at least one MyShape object.
  • Alternatively, we could explicitly tell the compiler that the typename of the inputs is to be MyShape: 
    System.out.println(GenericUtility.<MyShape>compare(t1, t2));

Comments

Popular Posts