Why Java Sucks

From Jonathan Gardner's Tech Wiki

Jump to: navigation, search

Contents

Introduction

Java sucks, a lot. Here are specific and highly relevant reasons why.

Disclaimer

A lot of people read this article, and think I am on crack or that I am just a grouch. After all, Java really isn't that bad of a language!

The bottom line is this. I've learned more languages than I can recall. I started at 8 years old with C-64 basic, then I learned C-64 assembler, then I learned Borland C anc C++. I did Win32 and MFC programming, even making a few DLLs and whatever the heck COM was back in the day. Then I learned perl, and along with that, BASH and the entire Linux ecosystem. Then I learned Python back in 2001 or so, and never looked back. After that, I poked at Lisp and Scheme and other functional languages, which improved my ability with Python.

I picked up Java without any formal training in it. I just read the manual and went at it. I was finding features that people who had been programming Java for years at the time weren't even aware existed. Because of my background, I quickly found many of the limiting features of Java.

You, who know Java and one or two other similar languages, probably have a hard time understanding what I am talking about at all. I'm like the math professor ranting against the physics professor because he refused to care about advanced theories when the answers to his theoretical questions were all answered there. The physics professor simply can't think at that level and so the math professor is like an alien to him. If I sound alien to you, it's because you can't understand what I'm talking about, in other words. That could either be because I am a poor communicator or because you don't understand enough about programming. I would hope you err on the appropriate side.

GC Sucks So Badly, That You Should Avoid Using Memory

An in-memory cache is a very logical, sane thing to do, especially when you understand how much memory you have available and how memory is managed. In C or C++, it is trivial to implement with really good results. In perl, python, and other languages, it is not so bad as long as you understand that memory may not be freed once it is no longer referenced.

In Java, however, in-memory caches should be avoided unless they are very small or very stable. See, garbage collection (GC) has the nasty habit of FREEZING the entire Java process. This was a feature that was added to make Java more "enterprise".

In the end, it just makes it useless.

Imagine a language where you are encouraged to use as little memory as possible and to avoid constructing and destroying objects. That language is Java.

I've had a kid Java programmer tell me he never had a problem with Java's memory management. This after spending literally months day-in day-out fighting Java with the top Java programmers at the large company I worked with trying to figure out why it randomly hangs. In the end, we were randomly changing things hoping to uncover the mystery behavior's source cause. And this kid Java programmer says he has never seen Java do that. Upon pressing, he said he had seen it do it, but he was able to figure it out. Well, how did you solve it? He mumbled something about configuring some esoteric file I already knew about. What parameters? He couldn't recall. In short, he had no idea what he did to fix it, but he felt like an expert.

Gah! Don't be that kid programmer!

UPDATE: I worked heavily on an android app for a startup. It suffered from glitches, especially randomly pausing for seconds at a time. It took a while to understand why that was. It turns out it is the GC causing the problems.

Java should not be used on devices with limited memory for this reason alone.

Worst of Exceptions and Value Checking

Java's design decision to use both null and exceptions for errors means you have to do all the work in C to detect errors, plus all the work in C++ to properly declare your exceptions.

Python and Perl have it right.

  • An exception can be thrown anywhere, at any time.
  • Never, ever use return values for error checking. (Perl does a lot worse at this than Python.)
  • If you don't like it, catch the exception and do something with it. If you don't know what to do with it, it's not your responsibility.

I would say about 30% of the Java code I write is error checking code and code to handle exceptions because Java really sucks at this.

A workaround that is actually semi-decent is to use RuntimeExceptions, or derivatives thereof, for all your exceptions. Of course, there are libraries that may throw something that isn't a RuntimeException, so you'll have to wrap every call to those libraries to convert their exceptions to a RuntimeException. But at least you won't have to write "throws Exception" everywhere.

One particular example of why, eventually, you'll end up using RuntimeExceptions everywhere is that there are very basic interfaces that do not allow exceptions to be thrown. For instance, iterators and comparators. If you want to throw an exception from there, you have one of three options:

  • Change the exception to a RuntimeException and just live your life with RuntimeExceptions.
  • Log the exception somehow, and check for it afterwards.
  • Change the exception to a RuntimeException, detect it outside of the call, and then change it back to its original form to be rethrown.

Won't cast an int to a long

Try this:

public void myFunc(long foo) { ... }
...
myFunc(0);

What do you get? An error.

name clash: X and Y have the same erasure

Try this:

import java.util.List;

class foo {
    public void func(List<Long> listOfLongs) { }
    public void func(List<String> listOfStrings) { }
}

What do you get?

name clash: func(java.util.List<java.lang.Long>) and func(java.util.List<java.lang.String>) have the same erasure

What does this imply? If you intend to use generics (comparable to C++ templates), forget it. You'll need different function names.

incompatible types

   [javac] XYZ.java:141: incompatible types
   [javac] found   : java.util.LinkedList<java.util.LinkedList<ABC>>
   [javac] required: java.util.List<java.util.List<ABC>>
   [javac]         return DEF;
   [javac]                ^

Apparently, interfaces are only good 1 level deep. Thus, any reasonably complicated data type makes interfaces useless.

static methods and members in a class

The reason why the singleton design pattern is so common in Java is because Java cannot handle static class members properly. It's simply absurd that you cannot override a static method or member in a derived class.

The take-away is that static functions are useless, so don't use them. Use Singletons instead.

Of course, singletons are simply a hack to get global variables. Other languages simply have global variables. Entire frameworks are written just to provide decent global variables in Java. That should be a hint that something is terribly wrong with the language.

The "class" type is an afterthought

If the "class" type behaved like any other object in Java, as it does in most OO systems (except C++), then we wouldn't have to use so many design patterns to work around Java's built-in limitations.

No control on member access

Java simply has no control over public members, unlike most other OO languages (except C++). That's why you have to write the following a billion times:

public Long getMemberName() { return memberName; }

Because, if, down the road, you decide you want to do something even remotely interesting with 'memberName', you're going to have to call a method to do it. So might as well write it now.

RSI

My wrists hurt when I write Java, even though I use ViM and type as little as possible.

"USE AN IDE!" you shout at me.

"Why are we writing programming languages that are not intended for human consumption?" I respond. Haven't we ALREADY PROVED that the CPU can do all the work in making things easy for us, the user, A LONG TIME AGO?!?

But apparently, we're supposed to live with one of the worst UI's in the world: the Java programming language.

Camel Case

One of my pet peeves with camel case is the following. Try to change "varName" to "memberName".

private Long varName;
Long getVarName() { return varName; }
void setVarName(Long newVarName) { varName = newVarName; }

You have to do two substitutions: once for varName -> memberName, and a second time for VarName -> MemberName.

If you used underscore case, then you'd have the following:

private Long var_name;
Long get_var_name() { return var_name; }
void set_var_name(Long new_var_name) { var_name = new_var_name; }

Now, it's a simple matter of a single substitution: var_name -> member_name.

Of course, I wouldn't have to do so much search-and-replace if there wasn't so much code duplication everywhere in Java. Conciseness is nice.

Class-centric

Whenever someone designs something in Java, particularly people that grew up on Java and C++, they have to think of everything as a class. They do not understand that functions are "things", too.

The worst example of this are classes that have exactly one method as part of its interface. This is no different than a function.

The justifications people make for this design pattern is absurd.

The cost in development time is atrocious.

Will people please recognize the lowly function as a first class citizen in the world of programming?

Iterators SUCK!

In Python, iterators have only one method: next(). The beauty of this solution is that you never have to implement a "hasNext()" method. Simply call "next()" and see if it throws an exception saying there are no more.

If you add the "hasNext()" method, then you have two entry points to iteration: hasNext() or next(). It turns out that they both need to do the same thing, so you abstract out the generation of the next item into a "generateNext()" method.

public class MyIterator extends Iterator<T> {
    private T theNext;
    
    private void generateNext() throws NoMoreElementsException {
        // generate the next value
        // store it in 'theNext'
        // throw an exception if none ...
    }

    public boolean hasNext() {
        if (theNext) return true;
        try { generateNext(); return true;}
        catch (NoMoreElementsException e) {
            return false;
        }
   }

   public T next() throws NoMoreElementsException {
       if (null == theNext) {
           generateNext;
       }
       T next = theNext;
       theNext = null;
       return next;
   }
}

(The above isn't perfect. The problem with the above is that you can't iterate across 'null' value, so you have to have a additional attributes to determine whether there are any more values, whether there is a next value, in addition to what it is.)

Notice, reader, that next() and hasNext() are exactly the same for any iterator in Java. Only generateNext() will vary in the slightest.

Notice, also, that hasNext() is really pointless. All it does is catch the NoMoreElementsException and tells you that it did. You can, and really should, do that for yourself.

Notice, also, that if you wanted to throw an exception from generateNext(), that the iterator interface in Java is completely incapable of handling such a thing.

Python totally kicks Java's butt in this area, and it's not even funny.

foreach doesn't take iterators

Enough said.

Function Pointers -- Missing

One of these days, I am going to write a class that looks like this:

public class Function {
    public Function(name, parameters, body) { ... }
    public ReturnValue execute(values...) { ... }
}

ReturnValue is either going to have the result or an exception.

Then I am going to implement my entire program on top of this.

Those of you who know Lisp see the joke for what it is. Except it's not a joke. If you want to have a reference to a pointer, you have to create a class or an instance of a class and pass that around.

Constructors can't call each other

So you have something like this:

class Foo {
    private final int bar;
    public Foo(int theBar) {
        bar = theBar;
    }
    public Foo() {
        bar = 1;
    }
}

Now, this is a pretty trivial example. I want you to imagine that the "bar =" line above is actually some complicated calculation. The second constructor is really a different way to initialize bar using the same calculation but with some slightly different parameters.

The bottom line is you can't do this, at least not in any sane way. You have to copy the code from the first constructor to the second constructor, turning Java into a language where certain bits of code cannot be abstracted away.

Two possible solutions:

First, we might imagine that we can dispatch one constructor to another. This is what Python allows, mostly because it is so laissez-faire about everything. This would be the ideal solution. You have one "master" constructor that does all the smart stuff, and other constructors that would dispatch to it. Of course, this is impossible.

Second, you might imagine a third method, a private method, that would do the actual constructor work. The real constructors would just dispatch to this. Great idea, unless you have "final" attributes as in the example above. You simple can't assign to final attributes unless you are in the constructor itself.

Note: See this thread for how you can (literally) call one constructor for another. Seems simple, right? Just use "this()". Well, it's not, and it's not clear why. I mean, the whole reason you would want to have a different constructor is because you do something non-trivial before you pass control onto the simpler and more basic and common constructor.

Methods With the Same Name as Constructors

What's wrong with this?

public class Foo {
    public void Foo() { }
}

What, you can't see it? Why, nothing's wrong, of course. The compiler accepts this just fine, creating a new class Foo with a single method of the same name.

What? You thought that was the constructor? Ha ha, joke's on you. It's not the constructor.

Run-time Dispatch: Fantasy

Let's say you have a class defined as:

public class Foo {
    void bar(Baz b) { }
    void bar(Boo b) { }
}

This has two methods of the same name but different behavior depending on the type of parameter passed. This is, of course, fairly common.

But what if Boo is a sub-class of Baz? Which bar does the following code call?

...
Foo f = new Foo();
Baz b = new Boo();
f.bar(b)
...

Before you give your answer, think:

  1. What is the most reasonable answer?
  2. What features of Java are reasonable and what features are unreasonable?
  3. Does this make Java a typically reasonable or unreasonable language?

Now, points 1 and 3 about should tell you that the answer is that it calls "void bar(Baz b)". Which is, of course, the most unreasonable answer since Java is unreasonable. Or, if you prefer, the reasonable answer since Java is a reasonable language in your universe.

No Globals means Frameworks

Java lacks globals. And because it lacks globals, every useful Java project requires a framework that gives you globals. So, in order to write the simplest app, you have to bolt-on thousands and thousands of lines of code and massive complexity just to get globals.

import is Useless

In Java, you have to name all of your classes uniquely, despite their namespace. Why? Because if you don't, you can't import them or else they will clash with another namespace's classes.

In Python, you have:

from foo import bar as baz

In Java, you only have:

from foo import bar

Which means if something.something.something.bar and something.something.something.else.bar need to be used in the same package, you're out of luck. You have to write "something.something.something.bar" ad infinitum.

No List Literals

This gets me again and again.

I want a list of integers:

[1, 1, 2, 3, 5, 8, 13]

But in order to do that, I have to write:

LinkedList<Integer> l = new LinkedList();
l.add(1);
l.add(1);
l.add(2);
l.add(3);
l.add(5);
l.add(8);
l.add(13);

The obvious way doesn't compile:

// Syntax error
LinkedList<Integer> l = new LinkedList({1,1,2,3,5,8,13})

Inner classes Don't Work

Inner classes sound like a good idea. Until you try to use them.

Java is So Hard People Prefer to Write Code in XML, Jython, Scala, and Clojure

One of the things that continually amazes me is how many people write code in XML to avoid Java. A lot of the frameworks is really a way to allow you to write code of one form or another in Java.

In other languages, such as Python, it is rare to see people write code in anything but Python. For instance, in the Pylons framework, there is a config file, but this is the exception and not the rule. Even then, the config file is there because it makes the job of configuring a Pylons app easier, not possible. That is, you want to set the database connection string or change where files are logged. Even then, the developers freely admit that they are trying to open up the app to people who refuse to learn Python but who don't mind writing Windows-style config files.

This is also the reason why Jython, Scala, and Clojure are rapidly becoming popular. Just wait until someone tells them that the JVM gives you NOTHING.

DNS Client Implementation

The DNS client that comes with Java is horribly broken. The default is to cache DNS responses forever. You can change this configuration to cache responses for a specific amount of time for all domains. VeriSign recommends its users who happen to use Java disable caching altogether because it is horrible broken. See http://www.verisign.com/stellent/groups/www_ndscs/documents/comnet_resource/030957.pdf

In a normal programming language, you can fix this. In Java, you can't.

Why Use Java at All?

If you intend to use Java, I encourage you to write down all the reasons you'd like to use it. Then check the list of things I made above and reconsider. Once you've done that, I would ask that you consider the following languages seriously:

Once you've examined those languages, if you still feel like you should use Java, I would appreciate to hear why. Please email me at jgardner@jonathangardner.net and let me know why it is you feel like Java will make your job easier.

Emails

I've been getting about 1-2 emails a week about this article for the past month. I don't know what's inspired so many people to write, but I imagine I've hit a raw nerve.

I'll basically categorize the responses I get.

You're an idiot

Argumentum ad hominem is a logical fallacy, and those who employ it have already lost the argument.

Translation: Name-calling is a sign that you lost.

You Don't Know Java

The experiences I've had above were not experienced in a vacuum. I was working at a huge corporation with many, many Java experts. I've consulted with them in trying to resolve my issues. Every single one I approached with the attitude, "You can't be serious. I must've understood something wrong."

After having writing 10's of thousands of lines of production-quality Java, I think I qualify as an expert, regardless.

Java is Popular

Argumentum ad populum is another logical fallacy, and shows you don't have any arguments left.

Translation: "Everybody is doing it" does not make it right.

Besides, Java is not popular. It is competing in a tight market of ideas and losing out to old and new languages.

Rock On!

You too. We're in this boat together, and those of us with senior-level experience usually end up cleaning the mess that junior-level developers leave behind. So let's try to get the word out.

You Missed Technical Point X

Of all the emails I have received, I received exactly one helpful one that actually argued against my observations. I've updated the content above to reflect and correct my mistakes.

Personal tools