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,
Serializablepublic
HashMap(Map<?
extends K,?
extends V> m)
Posted: Thu - May 13, 2004 at 01:55 PM
|