I've been playing about with Java, on and off, since it appeared in the 'HotJava' browser about five years ago. The language was simple and elegant with built in multi-threading, the virtual machine provided cross platform support, the environment supported dynamic loading of classes, and the libraries included powerful networking capabilities. The Java enabled internet browser held the promise of becoming a great platform for building network aware client applications.
Flash-forward to the present day and it seems that Java has not taken hold of the client side as expected, but of the server side. Application servers do the hard work of managing the operating system resources efficiently, leaving the application programmers to concentrate on solving the business problem at hand.
This has been my situation for the past four months, and it's been oddly frustrating. Mostly, I would have to admit, from me assuming too much about these technologies and not taking the time to study the details more carefully.
This editorial is a write up of my experiences so far. I'll structure them into the two broad categories.
Multiple Inheritance: I thought I'd miss multiple inheritance a lot, but it turns out that I mostly used this in C++ for defining interfaces anyway. I got burnt by the Symantec compiler many years ago, and learnt my lesson about relying on recently introduced features. That particular compiler could create some very strange v-tables. What I miss is being able to provide default implementations for those interface methods. I'm forced to write more code than I know I need to. Mix-in classes are a great way of factoring out common implementation.
Generics: I didn't realise quite how essential templates, and the STL are. The Java collection classes hark back to the Smalltalk libraries, where every object has to have a common base class - Object. I'd forgotten those early C++ days when the result of every 'get' call on a collection had to be cast to its real type, and sometimes you'd get it wrong, and very bad things would happen. At least Java does a type check for you, throwing an exception, but you don't get to find out about the problem until your server trips gaily through that piece of code. I'm having to relearn the old ways, the pre-template ways of programming in C++.
Destructors: I used to use destructors a lot, not just to clean up member variables, but as a callback point for when a static variable goes out of scope. It's so handy to wrap a dynamic object with a static handle object. You just can't forget to release the resource you've acquired. It just goes away when you reach the end of the scope. Finalizers in Java don't work the same way, they don't get called at the end of the scope, they get called when the garbage collector gets around to it, and from what I read, that may never happen. This would be alright if all resources had the same characteristics as dynamic memory objects, but they don't, some resources are far more scarce. For example: references to singletons, database transactions, file and network activity, operating system handles, timers, and synchronization primitives.
This last example is worth considering more closely. A typical technique for implementing threads and synchronization in C++ is to wrap the native system primitives in classes. You inherit from a thread class to create a threaded class, and you create a static object on the stack to have a lock held for the extent of the current scope. The Java implementation of threads works just the same way, but mutual exclusion does not. The synchronize keyword was introduced instead. True, it provides additional semantics, but I do wonder if it didn't come about initially because of the lack of destructors.
So, I have to remember to explicitly release everything I acquire. I can't add a keyword to have my transaction committed, or my timer stopped. I have to remember to balance my open's and commit's, and my start's and stop's.
Abstraction: The Java programming system provides a high level of abstraction from the underlying machine. In part this makes simple Java programs very quick to implement and get right the first time. But, hard programs are just as hard to write in Java as they are in any other programming system. In fact I seem to find it harder. Threading, synchronization, and memory management are all there, but there's a man behind the curtain not doing quite what you'd like.
Layers of abstraction are essential for solving complex problems, but without the intimate knowledge of what lies beneath it's very hard to reason about the performance characteristics of a system. An argument against my case here is that I may just be an old curmudgeon stuck in my own time when programmers started out hacking about on their Z80 and 6502 based home computers. Perhaps fifteen years ago the editor of Fortran-Fanciers-Monthly was bemoaning these new fangled C programmers who didn't know how to implement floating point arithmetic in assembler, or how to add new instructions to the processor by extending the microcode.
Strictness: The language provides positive reinforcement of good object oriented programming practices. That's great for a teaching language, but when you know what you're doing, and you want to bend the rules slightly, well, you just can't.
Virtual Machine - Performance: I don't think that Java programs run slowly, in fact it's quite impressive how fast they run, considering all the low level indirection that is being performed. I'm thinking about server code here. GUI apps still seem quite sluggish, but I'm told this is mostly architectural and implementation problems with those libraries. In theory Java code should run just as swiftly as C++ code. There's enough information there to translate it down to equivalent machine code. In fact, the latest version of the java virtual machine from Sun includes 'HotSpot' technology that translates commonly executed bytecode into native machine code at runtime. Very cool, but (and here's my moan) the development tools seem unable to deal with these complexities. Half my tools break every time I upgrade my virtual machine.
Virtual Machine - Cross Platform: The point of the virtual machine is to allow a single piece of compiled object code to run on many different platforms. This solves the cross platform problems found with lower level languages like C and C++. This is clearly a big win for client software developers, but not much gain for server implementers. Perhaps this creates a level playing field for server hardware vendors, but in my experience there are so many versions, variations, and ports of virtual machine, that you have little choice but to run on Solaris. All the porting problems have been transposed from the application domain to the configuration of the runtime environment. Getting the right mix of virtual machines for your tools and deployment platforms can be quite a challenge.
Memory: Where does it all go? It could be that Java objects have more associated meta-data than C++ objects, or that old unused objects are hanging about waiting for the garbage collector to zap them. The strict reference counting built into the language means that it's impossible to leak memory, but it is possible to leak references. And, it's very easy to end up referencing far more memory then you ever thought your server would ever need. Debugging these memory problems can be very painful. I have found no API that will retrieve the current reference count of an object. I'd like to make assertions about reference counts so that I can catch these problems early on.
Garbage Collection: This is a debate often visited. My view is that garbage collection is great if it works. However, different programs need different memory management strategies to achieve efficient use of resources to meet the performance requirements. Java was initially designed for short-lived client applications, an ideal environment for a garbage collector. Do I want my 24x7 server to hunker down every half hour to bloat up and page like crazy as it walks over all allocated memory? No thanks. This is how the garbage collector in the client side virtual machine appears to behave. Thankfully the server side virtual machine has an improved garbage collection algorithm that is well behaved for server applications.
CLASSPATH: Agh, this just drives me nuts, don't get me started.
Of course I realise I may be talking complete rot here, and it's just my Java-newbie-ness showing through, in which case I'd gladly entertain your repost to all this whining and moaning.
A quick update to follow on from my comments in Overload 40: In December the members of the European Patent Convention met to vote on whether software patents would be admissible. They decided to not admit software patents for the time being, but will reconvene in one year's time.
I want to point out that two of the authors of this issue are first time contributors to Overload. Oliver Schoenborn, and Antony Pinchbeck have presented a pair of interesting and well written articles. I hope this serves as inspiration for those thinking of writing up an idea, idiom, pattern, or tale of the software professional.