May 2004 Archives

Of the big changes to the JLS coming with JDK1.5, generics are definitely the one I'm least psyched about. The debate about this has raged for years, so I won't waste keystrokes rehashing arguments about how completely useless and ugly they are. Those arguments are now not only tired but moot as well; ready or not, generic types are here.

So, what are they going to do the code we have to deal with every day? Here's one case I worry about - consider the following:

import mypackage.Widget;

public interface MyBean {

  /**
   * Returns an unmodifiable List of the Widgets 
   * on this bean.  The objects in the list will 
   * always be instances of class Widget, but for 
   * some reason, I just don't feel like returning 
   * you a nice typed array per the Java beans spec.
   */
  public List getWidgets();
}

I think most people agree that this isn't a great API design, but sometimes it seems like the only argument against it that really sticks is that it isn't typesafe. Enter generics:

import mypackage.Widget;

public interface MyBean {

  /**
   * Returns an unmodifiable List of the Widgets 
   * on this bean.  Now it is perfectly typesafe 
   * so why should I bother with an array anymore?
   */
  public List<Widget> getWidgets();
}

Well, I would suggest that this is still pretty broken. It's still a bad contract. Even in this simple example, it exposes a lot more surface area with that List than necessary. This is why it relies on the somewhat hackish notion of an unmodifiable List. Also, the semantics of calling methods on that List are difficult to clarify, especially when MyBean's internal state changes.

(Moreover, it makes introspecting MyBean a bit more gross. Now you have to get the return type of the getWidgets method, check if Collection.class.isAssignable to it, and if so, call Class.getTypeParameters()[0] to figure out that it is a List of Widgets. I'd rather just call Class.getComponentType and be done with it).

But I'm afraid that we probably be seeing more of this kind of thing in the near future. Generic types may unleash a flood of bad design, as people start stamping out templated types without a thought for design minimization. But hey, as long as you don't have to cast, it's all good, right?

Sam has a lot of complaints about the forthcoming introduction of generic types into Java. I share almost all of those complaints, and even have a few more of my own which I will try to write about later.

However, Sam goes on to propose an addition to the java compiler which he calls Autocasting. The basic idea is to have the compiler insert a cast for you in an assignment by simply checking the type of the variable to be assigned. This only occurs on assignments - it can't reaonsably work on method call parameters, for example, because of method name overloading.

The only real benefit I can see here is that it occasionally saves you a few keystrokes. I argued with Sam that the downside is code that is harder to understand, which always trumps brevity IMHO. Sam counters that it isn't harder to understand, because a cast in an assignment is redundant - you already have declared the desired type in the variable declaration. Autocasting doesn't hide or obscure anything.

I found it hard to argue with this initially, until I considered the case of a living software system in which lots of people are making changes to source files. In the real world, explicit type casting is an invaluable safeguard against people on different sides of a design contract getting their wires crossed.

The example I presented Sam with was something like this:

  public class Pet {}

  public class Animal extends Pet {}

  public class PetOwner {
    public Animal getPet() { return new Animal(); }

    public static void main(String[] args) {
      PetOwner owner = new PetOwner();
      Animal pet = owner.getPet();
    }
  }

Granted, this is poor design - getPet() should really return a Pet. However, who can say they've never encountered poor design? (never in thier own code, of course ;) ). And say I were irritated enough with the getPet() method to want to go in and clean it up:

  public class PetOwner {
    public Pet getPet() { return new Animal(); }

    public static void main(String[] args) {
      PetOwner owner = new PetOwner();
      Animal pet = owner.getPet();
    }
  }

With Sam's Autocasting feature in place, the code in the main() method is going to continue to compile just fine; the Autocasting compiler will recognize that downcasting to 'Animal' from 'Pet' is valid, and so it will go ahead and add the cast for us at compile time. Our code will continue to compile and run just fine, without even touching the main() method at all. Autocasting just saved us a lot of trouble, right?

Well, yes, at least until we do something like introduce a new kind of Pet:

  public interface Pet {}

  public interface Animal extends Pet {}

  public interface Rock extends Pet {}

  public class PetOwner {
    public Pet getPet() { return new Rock(); }

    public static void main(String[] args) {
      PetOwner owner = new PetOwner();
      Animal pet = owner.getPet();
    }
  }

Now our code compiles just fine, but we are due for a nasty ClassCastException at runtime, one which will be all the more vexing because the line of code in question contains no cast.

Granted, this would not be a problem if we'd simply gotten our abstractions right on the first try. Actually, Sam probably does get all of his abstractions right on the first try - that's why he came up with Autocasting. :)

Unfortunately, the rest of us sometimes need to shift things around a little bit, at least early on in the development cycle. I think we need to hang on to all of the help that the compiler can give us.