At Object Design, I developed code in Java for over ten years, and I worked with Java more at BEA. I’d be happy to use it again. It has many strengths, as well as extensive libraries and some great tools. It’s a lot cleaner than many other popular languages.
Yet many people complain about it. I’ve been looking around the web seeing what the predominant complaints are. After filtering out the ones that no longer apply to the latest Java release, and the ones I don’t understand, and the ones that aren’t important enough to mention, I’ve come up with a list of current complaints that are interesting and have some validity. With each one, I’ve added some commentary. My comments are not deep; some are downright superficial. And they certainly reflect my own point of view, with which people can quite validly disagree.
- Base types (int, float, etc.) are not objects. So they can’t be passed to many useful classes that take Object arguments, and they’re otherwise treated specially and differently, leading to non-uniformity. The situation has been greatly alleviated by auto-boxing and auto-unboxing, although that’s something of a kludge.
- You can’t return more than one value from a method. If you want to, you have to return a little array (unless one value is an int and the other is a Person!) or an object of some special little class made just for this purpose. When I was helping Bill Joy and Guy L. Steele Jr. by reviewing drafts of the original Java Language Specification, I was originally upset that there was no way to do this. So I set out to find a small example program that obviously demanded such a feature, to convince them that multiple value returns must be added. I was unable to come up with one, and I could see that Java’s philosophy was to leave out things that are rarely used and not crucial, so finally didn’t say anything.
- Java is call-by-reference (in the same sense as Lisp) for object parameters. There is no implicit copying, the way there is in C++ when you don’t use a “*” in the type. Some people like such implicit copying in some circumstances. One example I was given was passing an Iterator, so that the called would not mess up the state of the callers Iterator. Personally, I did not find that example convincing: sometimes you really do want the iterator to be advanced by the method you call, and sometimes you don’t. Explicit copying seems to me far superior to complicating such a basic thing as parameter passing.
- Speaking of a called method messing up the state of an argument passed by a caller: for collections you can use Collections.unmodifiableList to make a read-only view of a List object to prevent the collection from being messed up. But that breaks the Liskov Substitution Principle: if the method’s parameter is declared to be a List, the method might be written to modify the List. An unmodifiable list is-not-a list. Should there be a Java interface for the read-only methods? Probably Josh Bloch has a well-thought-out discussion of this somewhere.
- There is no multiple inheritance of implementation. That is, a class can only inherit from one other class. There are no “mixins”. In my own experience, multiple inheritance isn’t used all that often, but it’s not all that rare either. Multiple inheritance of interfaces is truly critical, but Java has that.
- Java needs the Factory pattern because it doesn’t have polymorphic constructors as part of the language. In my opinion, many of the famous Design Patterns are conventions for extending the programming language, and so this is just a special case of that principle.
- Getter and setter methods are not built into the language. I interpret this as being another place where the designers of Java wanted to keep things simple, at the cost of not adding syntactic sugar. C#, the Microsoft answer to Java, does have these: classes can have “properties”. But if Java had “properties”, the reflection API would have to be more complicated to represent them, and so on. Whether these should be part of the language is one of those things where reasonable people can easily differ. For example, Flavors had this and CLOS does not have this.
- Variables declared in an outer class and referred to be an inner class must be declared “final”. This is a real shortcoming. It means that Java can’t do the most basic kind of “lexical scoping”. This would be even more egregious if Java programmers used more higher-level functions, but, as we’ll discuss, they usually don’t.
- Assignment is denoted with an “=”, which is confusing because it looks like an equality predicate. Something else should have been used, like Algol’s “:=”. I have no strong opinion about this, per se, since I don’t even like this kind of lexical syntax especially, but obviously it was a goal to look like C++ at this level.
- Java is case-sensitive. This is another issue about which people feel strongly either way, but no argument will persuade either side to change its mind.
- There is no operator overloading, in the sense of C++. Now, hardly everybody thinks that operator overloading is a good thing! From my own point of view, it’s unfortunate that there are special “infix operators” that are so different from methods, in cases where the infix operators have function/method-like semantics (”+” does, “||” does not). Operator overloading would let them be treated more like methods. But it can be confusing, especially for beginners.
- Integer overflow is entirely silent. This is bad.
- When working with streams, you often have to create all these nested objects, with classes like BufferedInputStream. The underlying reasons for all this seem sensible but the result does make it hard to do easy things, and verbose too.
- Checked exceptions stir up strong feelings in many people, some reasons being better than others. This is such a complex topic, and I am so interested in exceptions, that I propose to take it up in a later blog entry.
- Many important library classes cannot be subclassed, including String and StringBuffer, because they are final. I am less convinced than some other people that it’s really so important to subclass these, but I’m not sure.
- Arrays are not objects of any class except Object. You can’t subclass them and they hardly have any methods. There’s a class called Array full of static methods to do things with arrays. This leads to some non-uniformity, although in practice I don’t think this is a huge problem.
- It’s very awkward to “use functions as objects”. For example, there isn’t an easy way to apply some function to every member 0f a collection. Imagine trying to translate this Lisp program into Java: “(defun compose (f g) (lambda (x) (funcall f (funcall g x))))”, i.e. take two functions, each of one argument, and return a new function that is the composition of those two functions. You have to use anonymous inner classes with all the types properly genericized, at the very least, which is prohibitively verbose.
- Objections from Lisp and Scheme people: it’s all too verbose, there are no multimethods, expressions and statements should be the same thing, and there are no tail calls. There are no macros in the Lisp/Scheme sense; they could be added, as shown by Jonathan Bachrach and Keith Playford’s Java Syntactic Extender, but this hasn’t caught on, perhaps because the IDE’s would have to know about it. And, of course, Lisp/Scheme people don’t care for the lexical syntax.
There are probably other complaints. People love to complain about computer languages, which are all far from perfect. In the future, I’ll write a similar posting about Common Lisp.