Usability and Java 1.5 generics vs. Autocasting


Usability and Java 1.5 generics vs. Autocasting

One of the most often touted effects of 1.5 generics is that it will reduce the number of casts and make collections more usable and maintainable. I think that it has succeeded for the most part, but the cure may be worse than the disease.

At The ServerSIde Symposium I went to Graham Hamilton’s and Bill Shannon’s talk on usability in Java 1.5 “Tiger”. Basically, the argument goes like this, in Java <=1.4 you have to cast objects that are coming out of collections, this problem is twofold, you have lots of additional casts in your code reducing readability and you have runtime exceptions for casting the object to the wrong thing as it comes out of the collection. Here is some sample 1.4 code:

Map map = new HashMap(); map.put("key", "value");
String value = (String) map.get("key");

The equivalent in 1.5, if you indeed wanted the Map to be limited to string keys and values would be:

Map<String, String> map = new HashMap<String, String>();
map.put("key", "value"); String value = map.get("key");

As you can see, in a simple example we are actually specifying more types than in the 1.4 example. Some might say that as you increase accesses to the map, you eventually pay down this debt, but for most cases there will only be 1 possibly 2 places where you access the map. From a usability standpoint we don’t seem to have saved a lot. As you can see, the solution to the original problem is focusing primarily on the second issue, the problem of pulling things out of the map and casting it to the wrong type. This is an admirable goal for maintainability but I must confess that this basically doesn’t happen in most Java code. So in exchange for a one line comment on the map variable we’ve added a huge new syntax to Java that makes the code more verbose and, in my opinion, quite ugly. In addition, the 1.5 generics feature doesn’t solve this problem:

Context ctx = new InitialContext();
MyEJBHome1 home = (MyEJBHome1) ctx.lookup("ejb/myhome1");
MyEJBHome2 home = (MyEJBHome2) ctx.lookup("ejb/myhome2");

Since the lookup is truly not typed, we must do a dynamic runtime cast in order to ensure type safety. There is no solution within the generics framework for removing this cast. I have a counter proposal that gets us most of the way to readability Nirvana. I call it Autocasting. While experimenting with our Javelin compiler I found that I could correct errors in the framework instead of failing. For instance, let’s say I get code from the user that looks like this:

Map map = new HashMap(); map.put("key", "value");
String value = map.get("key");

Notice there is no cast on the map.get(). I could, in the compiler, automatically insert a cast on the right hand side to the type on the left hand side if the cast is possible. This would of course fail at compile-time if the LHS was incompatible with the RHS just as if you tried to cast an Integer to a String or something similar. At runtime it would fail the same way as if I inserted the cast myself, i.e. if the thing coming out of the map is not a String, you get a ClassCastException. This also has the property that you can write the above J2EE style code like this:

Context ctx = new InitialContext();
MyEJBHome1 home = ctx.lookup("ejb/myhome1");
MyEJBHome2 home = ctx.lookup("ejb/myhome2");

That code would be just as safe as before. Some people might argue that the cast forces people to think about it more clearly. I would argue that its just one more compile-edit step where the developer immediately goes back to the code and types the cast. Obviously in todays IDEs this isn’t quite as big a deal because it will tell you right away that you must cast it, but you still have the readability problem — no one likes looking at code full of extraneous information when it is clear you believe that the thing on the right can be assigned to the type on left.

I have talked with a bunch of people about this and the biggest argument against it that I have gotten is this code:

int length = map.get("key").length();

This would work with the generics, but would not work with autocasting. Autocasting would require you to write one of these:

String value = map.get("key");
int length = value.length();

or

int length = ((String)map.get("key")).length();

Where the second one is no better than what you have to do today. In the end, what I am really worried about, is adding all of this “generics” syntax to the language? I mean, have you looked at the javadocs for HashMap lately? That is enough to intimidate virtually anyone but a C++ programmer. If they aren’t careful Java will be crushed by much easier to use languages like Groovy just because the programs are about to get a lot more complicated.

Example Javadocs:

public class HashMap<K,V>
extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable

public HashMap(Map<? extends K,? extends V> m)