A Quick Critique of Java

by Brian Wilson 12/1/96 (http://www.codeblaze.com)

Intro:

This document is a quick brain-dump of my impressions of Java. I'm a fairly new user of Java, and my largest Java applet is only about 4,000 lines of Java spread over several source files. However, my full time "day job" is working on large multi-threaded C++ Motif GUI applications, so I formed my opinions fairly quickly.

Most everything in this document is my opinion, and your opinion is just as valid as mine. :-) So if you disagree, just whip up a little email and tell me why and I'll carefully consider your input. Also, since I'm so new to Java, I might have missed a capability or two, so let me know about anything I've overlooked.

And finally, I read the book "Teach Yourself Java in 21 Days" by Laura Lemay and Charles Perkins to learn about Java. Some of my criticisms are of that book, and not of Java. It's a fine book to learn the syntax of Java because it is clear, well-written, and organized well, but the authors are downright clueless about software engineering.

First, the Good Things About Java:

When I first started learning Java, I thought the designers of the language had really found that magical balancing point where there is everything you need, but the language is still uncluttered and easy to learn. The most positive thing I can say about Java is that the syntax is a lot like C++, with many of the bad parts of C++ left out. For example, there is no operator over-loading in Java, and no templates, both of which should never have been allowed into C++. In Java there are a few built in types like "int" and "float" and the associated literals like "5" and "3.14159". Also, there are no structs, just classes (structs are redundant).

The next nice thing about Java is that they built in several features that are lacking in 'C++', including a standard thread mutex syntax, and a standard syntax to spawn off threads. These are standard things that are available with virtually identical APIs in every environment, but since they are not in the 'C++' language specification they are ever-so-slightly different, so chunks of otherwise portable C++ code are not portable.

Also, like Smalltalk, Java comes with a standard GUI API. This has several problems (see below), but it does allow you to write end-to-end portable applications. I have done all my development of GUI Java applets on an Apple PowerMac, and without a single change they all compile on my SGI workstation, and wherever you compile them, they run on all platforms.

Also, I liked the idea of an "interface" to a class. It injects some practicality into the Class-Inheritance-Hierarchy. In Java, several classes can implement the same arbitrary set of methods (called an "interface") even though they are unrelated by inheritance. So, for example, instead of having the "print" method present in your base class which might not make sense for some of the inherited classes, a bunch of unrelated classes can choose to "implement the 'print' interface", and then you can call the standard set of "print" methods on objects of those classes.

The Worst Of Java: Performance

Java is worse than slow: it's painfully slow. This is (of course) mostly due to the fact that Java is emulated, but the Java language specification is also DESIGNED to be slow. For example, each array access is bounds checked, and there are no inline functions (or 'C' macros). Although Sun's marketing department has done one of the best snow-jobs I've ever seen regarding Java performance, anyone who makes the claim that Java is fast is either ignorant or flat out lying. At the very simplest tasks, I timed Java at a factor of 23 slower than natively compiled 'C++' and it just goes downhill quickly from there (when it isn't falling off some performance cliff) in real-world applications that involve file I/O or method calls or memory allocation. Here is my home-spun benchmark and my raw numbers:

        void arrayAssignTest()
        {
            int i, j;
            int theArray[] = new int[1000];  /* in 'C' just declared on stack */
        
            for (i=0; i < 200; i++)
            {
                for (j=0; j < 1000; j++)     /* use this column to test */
                {                            /* function call performance */
                    if (j % 2 != 0)
                        theArray[j] = j;     /* func1(j, theArray); */
                    else if (j % 3 != 0)
                        theArray[j] = j + 4; /* func2(j, theArray); */
                    else
                        theArray[j] = j - 4; /* func3(j, theArray); */
                }
            }
        }

        Array test took 0.065 secs and function call test took 0.167 secs in 'C'
        Array test took 1.5 secs and function call test took 3.75 secs in Java
        Both ran on same PowerMac 7200/90 (601 90 MHz), CodeWarrior 10 Gold 
        generated both binaries. Java was run in Netscape 3.0. Timings for 'C'
        were very reproducible, timings for Java varied wildly as might be 
        expected.

At a factor of 23 slower for the *FASTEST* you'll get your inner loops in Java, that's approximately like throwing out that 90 MHz PowerMac that can model moving 3D geometries in real-time, and replacing it with the Mac Plus running at 8 MHz you bought in 1985. That's throwing away 10 *YEARS* of hard fought performance gains. And with those performance gains, remember to throw out all the programs and new interfaces (like live-scrolling) that depend on that performance. Do you want to go back to 1985? Just run some Java applets, you can be there today.

One of the performance "cliffs" I fell off of in Java was appending a character to the end of a String. Because Strings are immutable, that involved allocating a new String object which was one character longer. I put that in an inner loop and WHAMMO, I fragged memory beyond belief, the garbage collector started going to town, and the applet performance tanked. This is probably equivalent to the mistake of putting a one character malloc followed by a free inside an inner loop in 'C', but I feel the abstraction Java provided led me into this performance problem (see the section on Garbage Collection below).

The Next Worst Part of Java: Too Many Pointers, Too Little Pointer Support

Java syntax resembles C++ syntax, except it is missing pointer math and has almost no pointer support. Of course, that's like saying FORTRAN resembles 'C', except for it doesn't have pointers. :-) Pointers are not some small after thought or accident in 'C', they are a fundamental part of one of the most beautifully powerful and elegant languages ever created. (If it wasn't obvious by now, I'm a hard-core 'C' fan. :-)

Although you might have heard that Java has no pointers, that's actually untrue. Java is riddled with pointers. You cannot declare a structure or object on the stack, but instead you must declare a pointer to the structure on the stack and then use "new" to allocate the memory for the structure in the heap, and then let the garbage collector free the structure sometime after the method finishes. Also, the designers of Java decided it was not useful to have an array of structures (hard to believe, but true), so in Java you cannot have an array of structures or objects, but instead you must have an array of pointers to objects which you therefore must allocate from the heap using "new".

In other words, the only way to refer to objects in Java is by pointers, but Java calls them "references to objects" in a lame attempt to pretend they are not pointers. Experienced programmers will see through this immediately, while Java will confuse less experienced programmers. Below is a Java code snippet dealing with the class "Point" which is a simple structure containing an "x" and "y" integer. This example shows how the Java designers gratuitously diverged from the C++ syntax in a misguided attempt to hide pointers from the programmer.

        Point p1, p2; // declare p1 and p2 as references to a "Point" obj
        
        p1 = new Point(1, 2); 
        System.out.println("x=" + p1.x + " y=" + p1.y); // print p1's coords
        
        p2 = p1;      // am I assigning a pointer? Or copying the obj??
        p2.x = 3;
        p2.y = 4;
        System.out.println("x=" + p1.x + " y=" + p1.y); // print p1's coords

In my opinion, the Java designers made a fairly large mistake when they decided not to copy C++ syntax more closely. Here is the same code snippet in C++ where I avoid using the shorthand "->" symbol for clarity:

        Point *p1, *p2; // declare p1 and p2 as pointers to a "Point" obj
        
        p1 = new Point(1, 2); 
        printf("x=%d y=%d\n", (*p1).x, (*p1).y); // print p1's coords
        
        p2 = p1;        // clearly assigning a pointer
        (*p2).x = 3;
        (*p2).y = 4;
        printf("x=%d y=%d\n", (*p1).x, (*p1).y); // print p1's coords

Although at first glance the Java notation *SEEMS* cleaner because it has less symbols, my claim is that it is not consistent and therefore less clear than the C++ syntax especially in more complex code examples. In C++, the two points are clearly defined as pointers that don't have associated objects unless you call "new Point()", while in Java this is not clear. Also, in C++ it is made clear when you de-reference the pointer to get at the contents, vs. assigning two pointers to the same object. The Java syntax is less clear, and depends on the situation which is annoying to say the least. Also, the Java syntax does not allow the useful structure copy: "*p2 = *p1" and in fact there is no way to copy a structure in Java (except by assigning each individual field of course).

One of the claims I've heard is that pointers and pointer math open a language up to security issues. Although this is used as an excuse, this would not cause the deviation from 'C++' syntax shown in the above example. The example shows that the changes were gratuitous, and had no real justifiable reason. The following quote from page 71 in "Teach Yourself Java in 21 Days" might shed some light on what the Java designers were thinking:

"There are no pointers in Java ... with Java arrays you have most of the capabilities that you have with pointers [in 'C'] without the confusion and lurking bugs that ... pointers can create."

This statement shows a complete lack of appreciation for the beauty and power and elegance of pointers in 'C'. Although this came from one book, I suspect it reflects the Java designers' attitudes towards pointers, which is that pointers cause confusion and are really not all that useful.

I could believe Java was designed by a bunch of LISP-heads, who never understood the whole darn point of 'C'. They didn't understand the elegance and speed and economy of syntax of walking down a list pointing to the CORRECT part of the object immediately with a dereference of a field and an "&" in the right place. And after my first 4,000 line Java applet, I hate them for their lack of understanding.

Java's Schizoid GUI Toolkit

Java provides a standard GUI toolkit, which means that you can write portable applets that will run on PC clones, Macintosh, and UNIX all with exactly the same source code. This is a good thing. The unfortunate part is that this GUI toolkit was designed by engineers from Sun Microsystems, which is world renowned for the most putridly awful graphical interfaces on the planet. Seriously, in the OpenWindows project that has now been abandoned, Sun created what nobody thought was possible: a windowing system that was WORSE than Microsoft Windows. If you ever want to laugh yourself unconscious, find an old Sun running OpenWindows and play with the scrollbar. But I digress...

The Java GUI toolkit is called "AWT" (a windowing toolkit?) and it's fairly easy to use when it does what you want it to. However, it feels as though the designers have not actually used the AWT to do anything, because it falls so short of providing a decent set of functionality. For example, it would be nice to have a one line way of posting a dialog with a simple message and an "Ok" button (and optionally a "Cancel" button). But that would make sense and be easy, so instead the Java designers force you to do the following:

To post a dialog, you must subclass the "Dialog" class. In Java you *MUST* have a new source file for each class, so to post a dialog just forced you to create a new source file and add it to your development environment. Inside your subclass, you have to override the event loop to get the ButtonPress events in the "Ok" button, which of course you had to add to the dialog by hand after calling the Dialog super-class constructor. Finally, you have to instantiate, and even *CENTER* the dialog by hand, map it, and worry about when it will be unmapped and freed by the garbage collector. Entering the realm of the weird, the "Dialog" super-class takes a "Frame" as an argument (for no apparent reason), so you actually (and this is gruesome) walk up your widget chain from your applet to find the frame which is hidden someplace up inside the Netscape browser. All to do what should take *EXACTLY* one line of code.

Another missing component any GUI programmer would miss within a few hours of using Java to create interfaces is the "Separator" widget (the horizontal line in a dialog above the "Ok" and "Cancel" buttons). And also there is no way of grouping widgets together like a "Frame" in Motif. Grouping of widgets appears in every modern interface I've ever seen (open up your Netscape "General Preferences" and you will see many groups of widgets), I cannot imagine why it was left out of Java.

But even when a component is present, the API is schizoid. You can draw lines in Java, and you can even draw any arbitrary edged filled polygon, but you cannot draw lines wider than one pixel?!! Huh?? That's insane. Every graphics system on earth can draw wide lines, except for Java. And Java provides fairly nice Font support, but doesn't specify that even *ONE* font will be available. To point out how silly this oversight is, the Netscape your applet is running in requires that a minimum set of varied fonts be available to any WWW page (for headings, body, fixed width text, etc), but Java applets have NO WAY OF ACCESSING those fonts. So the applet designer has no idea what fonts are available for use, or which fonts are recommended. One of the main reasons WWW pages look so much better than email is a small, well chosen, guaranteed set of fonts for headers and paragraph bodies. But Java screwed this up.

Method Parameters: by Reference or by Value?

All method parameters in Java are passed by value, just like in C (not C++). Of course, the reason 'C' could get away with this was the "&" operator, which is missing from Java. So how do you write a method that modifies it's arguments? Here is the one line function call in 'C':

        int i = 0;
        
        incrementInt(&i); /* elegance, grace, beauty and perfect clarity */
        
        /* complete implementation of "incrementInt" */
        void incrementInt(int *i) 
        {
            (*i)++; 
        } 
 

Here is the same code in Java:

        int i = 0, intPtr = new int[1];
        
        intPtr[0] = i; // wrap the call with gruesome array glue
        incrementInt(intPtr); 
        i = intPtr[0];
        
        /* complete implementation of "incrementInt" */
        void incrementInt(int i[]) 
        {
            (i[0])++; 
        } 

I'm mystified why the Java designers don't allow programmers to use the 'C' shorthand, which is just a little syntactic sugar on top of this disgusting hack. They must not think it is common to modify an argument inside of a function call? How would you return more than one value from a function call?

Java's Threads Disaster

A "thread" is a part of your program that can run concurrently with other parts of your program. For example, a "timer" thread could wake up every second and update a clock display in an application, even while the rest of the application is busy doing something else.

While this might seem like a handy thing that you could use all over the place, threaded code is INCREDIBLY hard to "get right" for reasons I don't want to get into. If you are thinking of putting threads into your application, DON'T!! Find a different way of achieving the same functionality. In the above example, a superior way to update a clock is through a "timer" callback which executes in the same thread as the rest of the application. But in a very, very small number of cases (less than you might think) threads are a necessary evil. In these tiny corner cases, Java supports threads, including the ability to guarantee mutual exclusion on a function, or on any random code block.

But the Java designers really screwed up when it came to threads. Java *FORCES* programmers to use threads by not supplying them with the very simple tools to avoid using them, such as timer callbacks and what Motif calls "workprocs". The ramifications of this boggle my mind. Java applets will be riddled with fatal bugs and instability because poor programmers were forced to use a capability I've seen precious few programmers master.

Here is a quote from page 361 in "Teach Yourself Java in 21 Days" found in the Multithreading chapter:

"A good rule of thumb is that whenever you're doing something that CAN be done in a separate thread, it probably SHOULD be... Threads in Java are relatively cheap ... so don't use them too sparingly."

In my 10 years as a professional software engineer, that is the most offensively irresponsible advice I've ever heard uttered from somebody who claims they know how to program a computer. The author of this chapter, Charles Perkins, is a moron. And not surprisingly, I noticed several bugs in his thread example code in this chapter. Sheesh! Here is another gem of a quote from Charles on page 354:

"threads have only recently been accepted into the mainstream. What's odd about this is that they are extremely valuable, and programs written with them are noticeably better, even to the casual user."

Charles is wrong. What I usually notice as a user of multithreaded applications is that they crash a whole lot, and are slower and less responsive than single threaded applications.

A final note about thread support in Java: the syntax for spawning a new thread is unnecessarily complex and limited. Most thread packages have the ability to "fork" your process into two parts, or launch a thread on a function call. This is simple. Java has this (misguided) idea that threads should relate to the object hierarchy, so you can only launch "Threads" that are objects, and therefore you cannot launch a method off as a new thread, but instead you must create a whole new class which will be run concurrently. Thread objects can inherit from the "Thread" class, or implement the "Runnable Interface" which has several obvious methods like "start()" and "stop()" and "suspend()".

Garbage Collection: the Bad Idea that Won't Go Away

The idea of Garbage Collection (GC) was pioneered in the LISP language 30 years ago, and it's been refined and improved quite a bit since then. After 30 years, it's still a really bad idea for any commercial system, which of course is why it's not in C or C++. Unfortunately, it's the only way to free memory in Java.

The idea of GC is that the running environment of a program can figure out when you aren't going to use dynamically allocated memory anymore, and free it at that point. It doesn't work well, because only the programmer really knows when the perfect time to free a large (or small) chunk of memory is. To decide when it is "safe" (not perfect, not even good, but safe) to free the memory, a thread called the garbage collector runs in the background chewing up some of your processor cycles constantly watching all the memory references. The larger your program, the more state, the larger the load the garbage collector puts on your system.

One of the biggest problems with GC in Java are class variables pointing to large chunks of memory. Object-Oriented languages like Java have class variables that are basically global variables. If you allocate a large chunk of memory and assign it to one of these globals, the garbage collector thread can't free it, because it doesn't know when you might re-enter the object at a later time and need the memory.

To work around this problem, you can give the garbage collector a hint by "nulling out" the pointer to the large chunk, and then in Java you can even invoke the garbage collector by hand to have it find the chunk and free it. So it's possible to implement large data-intensive programs in garbage collected object-oriented languages, it's just a bit more painful than using a non-garbage collected language, it's slower, and the overall system is much more complex.

Although I've heard that Java was designed with garbage collection "for security reasons", I find this claim disingenuous. You could easily design a system that uses 'C++' syntax to know exactly when a good time to free memory is, and use garbage collection technology to prevent abuses (like not actually freeing the memory until there are no more references to it).

Finally, the Java syntax seems to encourage you to allocate and deallocate memory, which means you will always be fragmenting your memory and invoking the garbage collector. For example, there is no syntax to copy one structure (class) onto another structure. Instead, you are supposed to "clone()" or even worse call "new" each time you need a temporary copy of a structure, and then let the garbage collector take care of it. In 'C', you could declare a temporary structure on the stack and use structure assignment to copy into it to avoid the "malloc/free" inside your inner loops.

Java Exceptions:

One of the more interesting features of Java is the ability to "throw" an "exception". Take the following example:

        try {
            openFile("tmpFileName");
            System.out.println("File Was Successfully Opened");
        }
        catch (IOException e) {
            System.out.println("File could not be opened: " + e.getMessage());
        }

An "Exception" is an error that occurs inside some function down deep in a call chain. The Exception is "thrown", and then the flow of execution goes immediately to the nearest calling ancestor who explicitly "catches" that type of exception. In other words, the calling method is given the opportunity to "catch" the exception, then the method who called that method, and so on. No code whatsoever is allowed to execute until the exception is explicitly handled.

In the example above, the first "println" would *NOT* occur if the "openFile" threw an exception, and the "e.getMessage()" in the second "println" might contain some clue as to the reason.

I go back and forth on how much I dislike Exceptions. On one hand, if they were used very sparingly and only for *REAL* "exceptional cases", they might be Ok. For example, UNIX has one global "errno" which is set deep down in a call chain and many times it is meaningless by the time it percolates up to the programmer. Exceptions have a better chance of containing decent debugging information, and they are extensible to contain many fields and methods (that you can happily ignore).

But Java doesn't just use Exceptions for "exceptional cases", and thus your Java code is littered with "try/catch" unnecessary visual garbage, because the moronic library writers thought it was a cute feature. For example, the way you know when you are done reading a file is when one of your functions returns an "EOFException". Excuse me? That isn't an exceptional condition, it will occur once per file!! Or in our example above, notice that the return value of "openFile" is completely ignored. Why not return an error in the return value, so the code is less cluttered with syntax?

In my Java applets, I tend to wrap 9 out of 10 "try/catch" sequences in a method that returns a boolean indicating success. That allows me to make a one line method call and check the resulting value in an "if" statement, without all the visual clutter of the "try/catch". Exceptions may be an Ok concept, but the Java library writers used them so much they are mostly an annoyance that must be wrapped to get it out of your code.

Portability

So you have created and compiled your Java applet, and tested it in one browser and it works perfectly. Are you done? No way... You may very well get radically different behavior when you run in a different environment.

I didn't use Netscape during development, instead I used the faster MetroWorks Java interpreter. After a couple days of work, I decided to run it under Netscape, and to my great surprise not a single GUI widget appeared!! Not one button, not one scrollbar. It ended up being a bug in Netscape related to layout...

Even when it would work correctly on my Macintosh's Netscape, the applet would appear (and behave) slightly different on an PC clone running Internet Explorer.

I would expect that this is due to the very young nature of Java and the browsers, and that in 3 or 4 more years they will all settle down and be very similar.

Final Word: Is Java the Future of All Computing?

Java might very well be an important part of the near future of computing, but only for very small, non-performance critical applications. The performance alone would doom Java to a very small corner of the application market, but the last nail in the coffin is Java's extremely limiting syntax. The lack of pointer support, lack of pass by reference parameters to methods, the schizoid toolkit, the lack of decent callback events like timers and workprocs, and garbage collection make Java painful to use to write any decent sized program.

Return to RandomStuff