Thursday, February 14, 2008

Java 5 Rant

Anyone who knows me has already knows that I'm no fan of Java 5. Since Java 5 was released, Java has dropped from 1st to 4th on my list of languages that I consider when starting a new application. It was such a disappointment to me, both because of the poor implementation of the new features, as well as the omission of some fairly basic features.

This is obviously an old rant. Java 6 is out and Java 7 is near. But Java 5 is where I feel the ball was dropped and the language took a tumble downhill. It's a bit of history and just my opinion. Don't take it too seriously.

DRAFT: There's a lot to this topic. I need to do a lot of clean-up and add some code examples to finish it off. I'll likely remove some of the exaggerated frustrations, but as I was typing it the first time, it's hard not to get excited. :-)

The Rant

Generics

There isn't much to say about Generics, as their failure is well documented. So, I'll simply say: they don't work. They don't offer any type safety whatsoever. At most, they offer useful information to frameworks about collection types via reflection. Otherwise, they're a more verbose, less helpful form of casting.

They really are verbose, especially when dealing with nested collections. The worst part is that it all has to be typed two or more times: once for the declaration, once for the instantiation and once for every method parameter you pass it into. If I'm going to do all of this extra typing, should it not at least be better than simple type casting?


The brilliant [sarcasm] part about it is that if you simply leave off the generic parameters at any point in your code, the code will compile anyway -- but suddenly without type checking. I fully understand this was done for backward compatibility, but this implementation is half-baked. I personally would not have bothered. I'd rather just live with untyped collections and casting. In the good old days, if I made an error by casting the return value of list.get(1) to something inappropriate, I would get an error right there, where I made the mistake. With generics, it's fully possible to type a list of Dates as a list of Strings in an attempt to use it in a for(:) loop, where the error will be reported. But the mistake I made could be far, far away from where the runtime reports it. Horrible, more verbose and less helpful.
But that's just the beginning. Google "Java Generics type erasure" and/or "Java Generics wildcards" for more fun times. It should not be easier to learn an entirely new language than it is to understand Java Generics in their messed up entirety.


Annotations

You would only know how bad they are if you've ever used anything else (e.g. C#). But they really are horrible.

  1. No extension of annotations. What? This is an object oriented language! Or at least it was. Now we have annotations based on what is essentially as limited as a C struct. I can't even begin to describe how horrible and limiting this is. There's no excuse. They simply ran out of time and decided to hammer it in however they could. See C# for a better implementation.
  2. No multiple annotations of the same type on a single target. Instead, we have to use collection annotations to group our annotations using a completely insane syntax. And yet this is one of the only places in the language you can use array shorthand! WTF. Seriously... again, see C# to understand what you're missing.
  3. No ordinal parameters (and only one messed up default "value"). Basically you should just never use the "value" default, as if you do, you've basically screwed yourself for all eternity. Since there's no way to depend on ordinal parameters to an annotation in the long term, you should just start using named params from the start. But that said, named parameters are horribly verbose. It would have been nice to have the option, but the definition syntax for annotations simply does not allow something like, oh I don't know, A CONSTRUCTOR!!! Why? Because they chose to use a bastardized interface as the definition for an annotation. And interfaces can't have constructors. I know why they used interfaces, I get it. But it's still stupid and they could have done far better. Again, they just ran out of time and decided to go with "finished" rather than "good". Which brings us to...
  4. The definition syntax. Look at the definition for an annotation. It doesn't even look like Java code. It looks like some other language created by Mork from Ork. Like what's with "default" suddenly being used to set the default value of a ... method? WTF! It's a bastardized interface. But remember, an interface is supposed to describe BEHAVIOR. But remember what I said earlier, annotations basically result in a C struct like thing attached to our class -- metadata (DATA!!!! not behavior). So why use an interface? Or @interface whatever the hell that is supposed to imply. This brings me to...
  5. Keywords. How the hell did enum get a keyword and not annotation? Let me tell you, I did have applications that had used the word "enum" as a variable name -- pretty much anywhere I ever used the Enumeration class! But I honestly had none that used "annotation". So why was there no keyword for annotation? But worse yet, why @interface? Why not @annotation if you needed to mangle it so that it didn't conflict with variable names? Or why not copy C#... hell, they copied Java enough -- AND DID A BLOODY FANTASTIC JOB OF IT. Just follow their lead and introduce a class, called Annotation that another class could extend... you know? It's really damn simple. I don't want to hear excuses. Watching the hoops that other teams have made Java jump through with Groovy and JRuby, and what Microsoft has done with C#, there are no excuses. Sun and the JCP ran out of time, money and the will to compete or something. This implementation is atrocious. I could literally go on, but that's enough about annotations.

Autoboxing

Integer a = 2;
Integer b = 2;

Integer c = 2000;
Integer d = 2000;


(
a == b) but (c != d).... nuff said. I'm not advocating using == on objects, but it's inconsistent. Autoboxing is simply incomplete and can never be fully or properly implemented without operator overloading.

for(:)

Again with the keywords... how do I even pronounce for(:)? Or type it in that sentence? Mostly the for(:) implementation is broken because generics are broken. But worse... why can't I iterate over an Enumeration or a damn Iterator?! If you want a perfectly good real life example, try using for(:) with anything that is returned from the Servlet API, like getParameterNames(). And contrary to popular belief (Ted Neward), there are APIs that return Iterators, that I would LOVE to use for(:) with, but I can't... because you have to give for(:) something (Iterable) that returns and Iterator, not an iterator directly. Why?! There's no good reason. Only academic ones.

A Note on Keywords and Compilers

There is really no excuse for not having keywords for annotations or foreach/in, as it's a simple compiler feature. A compiler can (nay, must) distinguish between different types of tokens in the code. C# 3.0 was able to make this work through simple pattern matching. Let's take the "annotation" keyword for example. Say you have code that uses variables called "annotation". What does that code look like?

public int annotation = 5;
annotation = annotation + 1;

Could you ever have a variable called "annotation" in the following position?

public annotation MyAnnotation {
...
}

Umm...no. The compiler can easily distinguish between that usage of "annotation" and member fields and local variables....easily. In fact, if you've ever had to name a variable "clazz", there's really no good reason other than that the compiler is lazy. C# managed to add LINQ and a number of keywords to the language without impacting other code, simply because it was easy to analyze the pattern that the code was used in, to distinguish between variables and keywords. I'm not suggesting that every keyword should be a valid variable name -- I agree, that could look a bit crazy after a while. But I think in the case of annotations and foreach/in we would have survived.

Enums

Enums are actually pretty good. They're well implemented, they have a keyword, and they are less verbose. Unfortunately they are also the least necessary and something I never really felt I needed. I've been using a type-safe enumeration pattern for a long time that basically does the same thing. But kudos, for making it less verbose. And the to/from string translations are nice too.

What's missing

Java 5 was missing some pretty simple, yet seriously nice things. A few that come to mind are:
  1. Multi-line strings. Come on guys! That's just a simple compiler change.
  2. Map and List literals... again, this is simply a compiler tweak. 99% of the time I use HashMaps and ArrayLists -- so just give me literal {} and [] blocks respectively.
  3. Type inference witha "var"-like keyword. Again, this would change nothing in the JVM, but would make generics far easier to deal with.
  4. The ability to reflect on parameter names. No it wouldn't require a change to the JVM. They could actually just automatically add annotations to the parameters at compile time, that carry the name forward in a runtime annotation. At least they'd get some use out of those bloody annotations.
The End.

And that's my rant. I could keep going, and offer real world code examples of why this sucks super hard. But Java 5 is old news.

I'm looking to Ruby, Groovy and C# 3.0 before I look to Java. Not so much because those languages are better than Java 5, but more because Java 1.4 was better than Java 5. Java is going downhill at the hands of Sun and the JCP. Sad, sad, sad...

To end on a happy note, I'll still often choose Java where it makes sense. It still can't be beat for application integration, high performance web services or anywhere server-side performance matters over all else (compared to Ruby, Groovy and Mono at least). But my choice is largely based on the JVM and the libraries available, and has nothing to do with the language.

Clinton