Java generics and constructor type inference
I started thinking about this over a curry with some colleagues who had some strong opinions on Java's generics. A lot of the flack directed at generics in Java, and even at Java itself, relates to its apparent verbosity. Take the classic example of instantiating a new Map.
Map <Integer, String> crudeCache = new HashMap<Integer, String>();
Why are the type parameters declared twice? Can't the compiler infer the types used in the call to the generic constructor from the reference declaration? This has been asked before, and there are a few proposals, but Neal Gafter's recent post seems the slickest to me.
He proposes this syntax
Map <Integer, String> crudeCache = new HashMap<>();
where the final <> is a signal to javac to do its type inference magic. I like that. The amount of typing is not that important - I haven't measured it, but I am sure I spend more time looking reading, and thinking about code than actually typing it. Anyway, I rely on my IDE to do a lot of boilerplate coding. However, this syntax looks much more elegant and readable. So, then why not go one step further and omit the <>:
Map <Integer, String> crudeCache = new HashMap();
Can't the compiler infer type in this case too? Unfortunately not, as this would be confused with a backwardly compatible call to a raw type constructor. Remember that raw types really are different from unbounded wildcards, so for compatibility with legacy code they need to stay. It is a pity that the least frequent use (raw type) would be the most terse.
Type inference in general is a pretty tricky problem. Java tries on occasion in a fairly simplistic way. Take for example
noStrings = Collections.emptyList();
In that situation javac manages to infer the type from the reference assignment. Interestingly, the implementation of the
emptyList method is rather crude looking. It simply casts a raw List into the desired type and hides this chicanery behind a
@SuppressWarnings("unchecked"). However, typing is not compromised - you can't ever get anything out of the list, as it is immutable and has zero size, so the apparently unsafe cast is sound.
When you not encumbered by backwards compatibility, simple syntax comes more easily. Take for example how Scala handles this issue:
val scalaMap = new HashMap[Int, String](1, "types inferred")
Whilst that concise syntax looks like it comes from a dynamically typed language, Scala is as statically typed as Java. But as the type system is so well constrained, the designer (who was on the original Java generics "GJ" team) has been able to use local type inference to provide both the generic type information, and the type of the reference declaration itself. I am impressed by the clean look, but I think I'd miss the reference type on the left had side of the assignment as it must help when reading through the code. Interesting to see what can be done on the jvm when you start with a clean sheet though.
Labels: java generics