But I like it. In fact, I will without hesitation say that it's one of my favorites, out of the dozen or so languages that I've used professionally and as a hobbyist.
Sure, it has issues. It's (give or take) 13 years old, and every 13-year-old has issues.
I'll grant that it's verbose: as a C programmer used to function pointers, I was vaguely annoyed at having to wrap my function pointers not only with a class, but an interface. But on the other hand, I liked that those function pointers — err, functors — were strongly typed, and could be stored in a pre-built hashtable for easy access. And now, my IDE will spit out the boilerplate and my eyes will skim over it.
I'll grant that the API has gotten a bit crufty over the years, in part due to a desire for backwards compatibility. And parts of the API are wonky to begin with; if you've ever worked with the
javax.xml packages, you know what I mean. Or if you've ever found yourself explaining why it's a bad idea to subclass
Thread, even though it gives you a
And auto-unboxing is the spawn of the devil. Actually, that may be putting it too lightly. I have no problems with auto-boxing, and understand why people found it useful (although I still scratch my head at the reasons for a
List<Integer>). But when “
if (foo)” can throw
foo is a primitive, there's an issue.
But every language has its warts, even LISP (unless you consider those long strings of closing parentheses to be a bonus).
The reason that I like Java is that it has a purpose, and the language structure is driven toward supporting that purpose. The result is an internal consistency, a clean mental model of how a program should be structured. In my experience C, Python, and PDP-11 machine language have a similar consistency, while C++, Perl, and the 8086 don't.
For Java, that purpose was support for a graphical user interface, although in many ways it's morphed into support for web applications. Fortunately, these two applications share many of the same needs.
Foremost is that they process independent events. The core of a GUI application is the event dispatch loop: the user's actions (mouse clicks, keystrokes, &c) are packaged as an event, and handed to a GUI component (button, textbox, &c) for processing. In a web app, the incoming request is similarly handed off to a servlet for processing.
This leads to use of objects as independent actors, and dynamic dispatch of all methods. The event queue doesn't (and shouldn't) know what type of component is going to handle a mouse click, nor what that component will do as a result. It simply calls
processEvent(), relying on dynamic dispatch to handle the details of who and what. Conversely, there is no reason for the
JButton to care whether it received that mouse click from actual human interaction, or because a
Robot injected it into the event queue.
Second is that objects use data, and in most cases they don't expose that data. The fact that a component occupies a particular piece of screen real-estate should be of no interest to anything other than the framework. However, the framework needs to work with positions, and quickly too.
This leads to the existence of numeric primitives and arithmetic operations. Why would anyone want to pay the overhead of object creation when recalculating a component's position? Or be limited by the set of operations that the language designers chose to support? (after all, if there are no primitives, there's no way for the end user to implement her own functions without changing the native library)
Third is easy access to threading. The event dispatch loop does not react kindly to long-running operations, such as a database query. If a query is executed inline with event processing, the UI will freeze waiting for it to complete. By building threading into the language, Java made it easy (at least in theory) to spawn off these long-running applications, allowing them to return results on their own time. To anyone who remembers the multiple threading models that a C program faced in the mid-1990s, this was a huge step forward.
There are more features driven by this original goal. Some are not clearly beneficial: checked versus unchecked exceptions comes to mind quickly (although checked exceptions can at least theoretically be corrected by end-user action, so the consistency remains). Some features, such as inner classes, appear to be driven by that goal even though they were added much later. And, interestingly, many of the later features that don't quite fit (auto-boxing, anyone?) seem to be driven more by a “me too” philosophy, rather than support for GUIs.
And I think this original goal plays a large role in why I like Java: it's not just the internal consistency, but that my professional career has been tied to user interfaces. I started my first full-time job in January of 1984, working on the then-unannounced Macintosh. And the event-driven GUI model turned my previous linear-flow programming ideas on their head. Through the 1980s I worked off-and-on with GUI platforms — Macintosh, Windows 1.0, X/Motif — and focused on writing autonomous C methods. In the 1990s I switched to C++, but never quite “got it” — sure, I liked the close binding between methods and their data, but that was what I was already doing. Since virtual functions were a pain to implement and generally discouraged on a performance basis, I rarely used them (and, perhaps as a result, never built deep class hierarchies).
Coming to Java in 1999, a lightbulb switched on in my head: this is why object-oriented programming is cool. Interestingly enough, what made that lightbulb switch on was not a GUI but the output from a parser generator. But that's a story for a later time.