Wednesday, April 3, 2013

Java Generics Trivia: A Tale of Syntax Discovery and Clean Code

I have spent years working with Java, although never as intensively as during my graduate school years. A significant part of my effort in graduate school was in gaining a deep understanding of the semantics of Java. There have been a lot of changes in Java since then—generics being among them. Today, while doing some programming, I came across something that I didn't know you could do.
I am writing a generic fixed-size grid class: it contains width*height elements that are indexed by <column,row> pairs. When faced with the problem of making a grid in a specific size, my first thought was to do it this way:
class Grid<T> {

  public static Grid create(int width, int height) {
    return new Grid(width,height);
  }

  private Grid(int width, int height) {
    ...
  }

}

However, I am also trying to follow Clean Code as closely as I can. In particular, I wanted to reduce all my methods to zero or one parameter, so I decided to introduce a builder here instead of the static factory method. In fact, I had just shown a similar example in CS222 last week—though, notably, without generics.

My refactored version, then, looked like this:
class Grid<T> {
  public static <T> Builder<T> width(int width) {
    return new Builder<T>(width);
  }

  public static final class Builder<R> {
    private final int width;

  private Builder(int width) {
    this.width = width;
  }

  public Grid<T> height(int height) {
    return new Grid<T>(width, height);
  }
}

Isn't it pretty? No method has more than one parameter, and the structure of the method calls enforces that I cannot get a Grid object without specifying its width and height. I returned to my calling code—a private createGrid utility method within my test suite for this class—and replaced the call to the static factory method with the following. (Note that Integer here is the data stored in the grid and is not related to the int width and height; the dimensions are always int regardless of the stored data since I am dealing with a discrete grid.)
Grid<Integer> grid = Grid.width(10).height(10);

I was surprised when the compiler complained. It sure looks a lot like the handy type-inferring methods of Guava such as Lists.newArrayList, that keep me from having to pepper new and angular braces throughout my code. Yet, the compiler insists that it cannot convert a Grid<Object> into a Grid<Integer>.

Clearly the problem is with the generics. To test my understanding of type inference, I converted the code to the following:
Grid.Builder<Integer> builder = Grid.width(10);
Grid<Integer> grid = builder.height(10);

This worked fine, but it obviously is not the kind of code I want to write when creating grid objects. If I was happy with this kind of awkwardness, I wouldn't have pedantically followed Clean Code in the first place.

I could not see a clear escape from here, but I began to wonder what kind of syntactic stunts I could pull to get this to work in fewer characters. I tried the following on a whim:
Grid<Integer> grid = Grid.<Integer>width(10).height(10);

If this kind of code showed up in Naftalin and Wadler's book on generics that I read six years ago, I certainly don't remember it. I pride myself on my mental Java compiler, but my brain still stutters when I look at this code.

The good news is that it works, and I learned something about the language in which I like to claim expertise. (I suppose I knew enough to know what to try, and that says something.) The bad news is that there is still a DRY violation in the generic types. If you can see a way to eliminate that, let me know!