Complaints I’m Seeing About Java
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.
Tags: Java
December 8th, 2007 at 10:47 pm
Three things I’d nominate for the Java-grumblings collection: CLASSPATH, finalizers, and slow app startup. Did you consider and reject any of these from your list?
December 9th, 2007 at 12:08 pm
Recently I came to see multiple return values as a future-proof feature. You have a function that computes some value and has a number of callers. One day you realize you need some auxiliary value from inside that function, or which is cheaper to compute inside the function, but not all callers care about. In Lisp, you can just go ahead and pass it back and change only the callers that are interested in it — or none of them. In my case it had to do with pagination for a website; the caller usually only wanting the list of items but sometimes wanting to know whether or not there are more items in the past.
If you have to stop your program and rewrite a substantial portion of it, recompile it and redeploy it, it’s harder to make a case for multiple return values. You already have to recompile, so you might as well change all your call sites. I would probably not design a function an API using multiple return values over just returning a list unless it was clear that the vast majority of callers would just take the first return value without any interest in the rest.
December 9th, 2007 at 10:47 pm
Jeremy: CLASSPATH and slow app startup are implementation problems, not problems with the language.
I’d add a big complaint, though: Generics are fake. You can’t do this:
class A {
… new A[123] …
}
December 9th, 2007 at 11:51 pm
schani: CLASSPATH is fundamental to the Class Loader model that Java employs. You could argue that it’s part of the libraries, rather than part of the core language, but you can’t find an implementation, nor make an implementation that’s certifiable as Java (TM), without CLASSPATH.
I haven’t studied the issue closely, but I hypothesize that Java’s relatively long startup time (compared to C++, say) is the result of two things. First, there are some hard-to-avoid consequence of the bytecode representation of Java programs. First, to enforce static typechecking and other safety properties, the bytecode for a newly-loaded class has to be verified with a static analyzer. Next, if you’re going to compile that bytecode to native code on the fly, you have to spend the time compiling. If you’re going to interpret it, that’s slow too. Finally, there’s the interconnectedness of the libraries: to put up a simple GUI using Swing, you’re going to load hundreds, if not thousands, of classes.
Finally, I don’t understand your complaint about generics. Your example seems to involve just array creation. Perhaps angle brackets in your example got eaten by wordpress?
December 10th, 2007 at 10:45 am
Startup time can be reduced dramatically by ahead-of-time compilation, as they are with Microsoft’s CLR, for example.
Yes, angle brackets get eaten. Maybe this works:
class A<T> {
… new A<123> …
}
December 10th, 2007 at 10:47 am
Ok, again
class A<T> {
… new A<T> [123] …
}
Doesn’t work in Java. No problem in ML, Haskell, C++, C#, …
December 10th, 2007 at 12:26 pm
# You can’t return more than one value from a method.
True, but only very little work is required to create a Pair and Triple classes, which cover 95% of use cases where you need to return more than one value. And if you really need more, it’s acceptable to use an array.
It would obviously be nicer if java had syntactic sugar to turn
return new Pair(t, u);
into
return (t, u);
But it’s not a show-stopper.
December 10th, 2007 at 1:11 pm
Martin: I assume Pair would have two instance variables of type Object. That’s pretty good except that it doesn’t work for primitive types, although perhaps the auto-boxing/auto-unboxing hack would take care of that. A generic Pair, that lets you declare the types, obviously has its pros and cons too. Yes, I’ve seen people use 2-arrays of Object for this. Yes, it’s not a show-stopper, which is why I didn’t end up suggesting it to Joy and Steele.
Schani: Good point! I had not thought of that.
Jeremy: Yes, CLASSPATH is as good as being part of the language for all practical purposes, although I think it was always the idea that you could have an alternative mechanism, e.g. if code lived in some kind of DBMS, and that would be considered kosher Java as far as the standard is concerned. I’ve never seen any Java that doesn’t use files and CLASSPATH, though.
A big problem with CLASSPATH in practice is that when you get it wrong, you don’t get clear error-message feedback. If there’s a typo, it’s just ignored. And since Java loads everything on demand, dynamically, you can’t really be sure that you have your CLASSPATH right until you have executed every single code path in the program (as my friend Dave Moon just found out recently). It would also be nice if there were a way to say “find all the jar files in this directory” instead of having to list each jar file. None of these things is inherently unfixable, but an awful lot of time has gone by without their being fixed. It’s surprising that I did not find grumbles about this when I surveyed the web; maybe I didn’t look hard enough, or maybe everyone is numbed at this point.
So what’s wrong with finalizers? Just that people don’t really understand what they are, and the fact that they’re not guaranteed to run? I don’t think they’re very useful but I don’t think they get in the way.
I don’t know whether the byte-code-verifier is turned on by default when you invoke Java from the command line; I don’t think it is any more. But surely the slow startup does have to do with all the classes that are loaded at startup (try java -verbose:class XX sometime), and that the JIT compiler has to run for the first time on so many classes/methods. There have been “global Java compilers” that do all this and then dump executables, but none of them seems to have gotten any traction for some reason. I think BEA’s JRockit implementation has some features along these lines but I can’t remember any more.
By the way, JRockit is worth trying out. The last time I hacked compute-intensive Java, it was about twice as fast as the regular Sun implementation! It’s free from BEA. The group who develop it are very careful and smart, and they’ve done a lot of work on interesting GC algorithms too. It has some nice monitoring/measurement features as well. Google for “jrocket” and click the first things it finds.
December 10th, 2007 at 1:23 pm
You summed up the shortcommings of “Java The Language” pretty well.
Yet what really bugs me is “Java the OS” and “Java The VM”.
Sun designed a VM and APIs as an operating system substitute for set top boxes. Trying to morph it into a Windows killer was a big mistake, repositioning it as a VM for network application was Worse Than Failure.
Now Java developers have to fight their way through to access the real OS beneath, battle with the JVM’s inadequate concurrency model, be ware of its insane memory requirements and cope with the side-effects of its broken garbage collector.
December 10th, 2007 at 1:26 pm
What’s wrong with finalizers?
You have defended Java’s lack of multiple return values by saying that it’s a feature that’s not needed often, so to keep the language simple they left it out.
I could argue that finalizers are not only needed seldomly, they are completely useless. What do you do with a piece of code that might or might not execute, and if it does, does so at some unspecified time? We might just as well introduce statements for which you can specify a chance of execution, say 30%, like in Intercal.
They don’t get in the way if you don’t use them, obviously. The problem is that people do use them, and they use them wrongly, namely to free resources.
December 10th, 2007 at 1:40 pm
> It would also be nice if there were a way to say “find all the jar files in this
> directory” instead of having to list each jar file.
Have a look at Java Service Wrapper.
December 10th, 2007 at 1:51 pm
The biggest complaint for me is pretty fundamental and that’s that Java is statically typed, which imposes a huge verbosity overhead and means it takes me more code (and hence more time and more bugs) to solve a problem than in a dynamically typed language such as Python.
December 10th, 2007 at 2:42 pm
Finalizers are very useful for where external resources need to be taken care of and where things might slip through the cracks.
Take, for example, an external database — you might want to shut down the connection gracefully, and you might not be able to guarantee that all code-paths will do this explicitly.
The problem with Java finalizers is that they invoke a method upon the object to be deceased. This can resurrect the object … and an object will only be finalized once.
This is probably mainly due to Java’s lack of closures, which is how finalizers usually operate — close over the resources that need clean-up in special cases.
Python has a different form of finalizing brain-damage (due to confusing finalizers and destructors), which results in objects with destructors not getting garbage collected — at least Java avoids this stupidity.
December 10th, 2007 at 5:09 pm
Implementation problems and gripes with Java the VM matter.
Java on the desktop didn’t fail to catch on because users hate programs written in languages that don’t have multiple inheritance.
December 10th, 2007 at 5:29 pm
Dan: you’re right, people are numb to CLASSPATH problems now. I think that it mostly bites people who are new to the language, and just can’t figure out why they’re losing. After you get some experience, you recognize a CLASSPATH screwup by the symptoms and just fix it. Your complaint that you never know if you’ve done everything right until you execute every code path is a complaint that usually gets leveled against more dynamic languages, but you’re entirely right.
Everyone, re. slow app startup: I certainly grant that it should be technologically feasible to speed this up a lot. In practice, I theorize without proof that the problem is that most people using Java are doing so server-side, where startup time doesn’t matter. There are still vanishingly few desktop Java apps. Eclipse is the only one I know of that has wide name-recognition.
Finalizers: Complaint 1 is, because they’re not guaranteed to run, they can’t actually be relied upon to keep you from, say, running out of file descriptors. But this is exactly the sort of thing they’re recommended for. Complaint 2 is that the semantics are ugly: a finalizer can run once, reinsert its object into the set of reachable objects; but the finalizer won’t be run again afterward. Basically, they’re harmless as long as they’re not used, but there are few good ways to use them. But they LOOK like they solve a problem. So they’re an attractive nuisance.
December 10th, 2007 at 5:43 pm
You do realize that all the little excuses you are making can just as easily be used to defend to program in C, or worse ASM?
The thing is, people that complain about Java, usually program in stronger languages (Haskell, ML, Python, Lisp, Ruby, Scala, etc.).
Where you keep referring to ‘its not that much used in practice.’ .. Please read ‘is not that much used by Java programmers, because they don’t know any better’.
Any decent framework is 90% polymorphic code. Yet you claim the factory pattern is _good enough_. It’s definately not.
The thing is, Java is statically typed, but without Generics the static typing is purely an annoyance. We are forced to supply types, but all type-checking is deferred at run-time. Hereby combining the downside of both statically typed languages with the downside of dynamically typed languages. Thankfully they fixed that with generics. But the popularity of the language shows that current Java programmers have to be pretty clueless, because they were already, by choice, using a language that was very much broken at the time.
No amount of excuses is going to save Java though. It’s cumbersome, limited, programming languages.
The quality of programming languages and their implementations are the relationship between type-safetety, performance and lines-of-code-to-accompllish something. Programming languages that are safer are generally more cumbersome (require more lines of code). Programming languages that are easy (require fewer lines of code) are have slower implementations.
It’s this relationship that is completely messed up for Java. Its too much lines-of-code-for-trivial-tasks without giving enough safety garantuees in return. Its too slow to warrant this amount of lines-of-code either.
So whatever you need, Java is _not_ the tool for the job. Use C when performance matters, Haskell/ML when safety matters or Python/Ruby when easy of programming matters.
December 10th, 2007 at 6:21 pm
And while we’re at it, here’s a huge complaint, actually I would go so far as to call it a monstrous design bug: null. Why do we need null? Just so that now and then we can enjoy a NullPointerException? Languages like ML and Haskell do fine without null. Why does Java (or C# for that matter) need null?
Or let’s approach it from another angle. What type of argument does this method take:
void write (string text) …
A string? No, not entirely correct. Either a string, or null. How come? Why is it important to be able to pass null to a method which takes a string? null definitely is not a string, so that breaks the Liskov Substitution Principle mentioned in the article. To say nothing of the NullPointerException you’ll get when you run the program. Or the null pointer check the JIT has to generate because it can’t rely on the argument being an actual string.
I’d really like to know what the rationale behind null was.
December 10th, 2007 at 6:32 pm
What about this complaint?
http://steve-yegge.blogspot.com/2006/10/egomania-itself.html
December 10th, 2007 at 6:58 pm
I have a couple to add:
1. No multiline strings. You either have to a) abuse the string ‘+’ operator, or b) declare the content in some external resource. While an external resource might be good practice for a large i18n application, its overkill for exploratory development or a small application in which you need a long string that is only used internally by a single class.
2. Java makes it hard (not impossible, but hard) to add jars-inside-of-jars to your classpath. This would be the easiest way to deploy a known-good set of third-party dependencies though.
Also, I disagree with you on the topic of returning multiple values. It is far better to return an anonymous, immutable, statically types collection of n-values (tuples), than to return an ArrayList which *should* contain some convention of values, but isn’t type-checked to guarantee it. I can declare Pair, Triplet, Tuple4(T1,T2,T3,T4) in my Java applications to work around this hole.
December 10th, 2007 at 7:25 pm
Why nulls?
I find to use the null value a lot actually. Let’s say the prototype for a method I have is Person findPerson(String firstname)
It looks for a person on the DB based on the firstname, fills up a object, and returns it.
What happens if a person with that name does not exist in the db?. You have two options:
Return a empty Person object
Return null
I find it easier to check for if ( (p=findPerson(“foobar”))!=null) than checking for p = findPerson(“foobar”) ; if (p.getName()!=”");
Well, thats how I see it, but I’m still in college, so take it with a grain of salt
December 10th, 2007 at 7:39 pm
Complaints Im Seeing About Java
This story has been submitted to Stirrdup. Your support can help it become hot.
December 10th, 2007 at 8:27 pm
@scani
You are correct.
But consider the consequence?
They gave up all of type-safety to make this null-thing the default.
All of it. But they didn’t remove the obsolote code to check types. They still enforce you to specify types.
You get all the downsides of static type-checking without gaining any type-safety at all. Since a simple null value will break any valid (according to java’s idiotic type-checker) function.
How could a language be more clueless?
December 10th, 2007 at 10:28 pm
My guess for the rationale behind null was so the runtime doesn’t have to check for reading an uninitialized field. Initialize it to null and make that type-valid by fiat. Yes, that is stupid, it just moves the runtime overhead from one place to another. And an UninitializedFieldException at the point of error would certainly be more debuggable than a NullPointerException later.
It is in fact very often useful to have method arguments or results that can be either a class instance or false. Since Java’s type system does not permit that, you can use null instead. But wouldn’t it have been better to make the type system provide what people need instead of institutionalizing a workaround?
December 10th, 2007 at 11:18 pm
[...] Complaints I’m Seeing About Java « Dan Weinreb’s Weblog (tags: java programming languages) [...]
December 10th, 2007 at 11:44 pm
Well, initialization can certainly be enforced in the constructor. C++ has references, which always have to be properly initialized, and despite the ugly syntax it works.
Yes, sometimes it’s useful to have arguments which can also be nulled, and that’s where you could introduce either an ordinary class for that (like the Maybe type in Haskell) or have nullable types (like C# does for value types).
December 11th, 2007 at 3:00 am
Those frustrated with the limitations of Java should investigate Scala (www.scala-lang.org). It runs on the JVM and interacts well with Java, but it fixes most of the complaints above, as well as many others.
December 11th, 2007 at 3:05 am
*) Placing a jar file in the directory specified within the jvm runtime property ‘java.ext.dirs’ will automatically make that classes in that jar file available at runtime. I do not recall if that property can be specified at launch via -D, my guess is it can. This still leaves many issues unaddressed, but does provide a default directory from which all jars are processed.
*) If insuring a correct classpath (or insuring that a particular version of a library is used and not a random version) is critical then writing an intelligent classloader is a (relatively) trivial solution.
*) IMO Generics are the biggest disappointment with the java language for exactly the reasons others have already mentioned.
*) Java SE 6 Update N (formerly known as the “Consumer JRE” project) attempts to address the startup issue others have mentioned. I have no experience with this, only the opinion that it’s too late for java as a legitimate choice when writing a client (like) application.
December 11th, 2007 at 6:10 am
Fernando: you wrote “What happens if a person with that name does not exist in the db?” There’s a third option, which I consider the best: you throw an exception. Then you don’t have to deal with the null value or constructing a special empty Person.
December 11th, 2007 at 7:59 am
Jeremy Brown: It is possible for a method to throw an exception instead of returning null, but this isn’t usually done for two reasons. 1: Using exceptions for routine control flow is considered bad practice as the code suddenly jumps from one place to another. 2: In the current JVM implementation, exceptions are quite slow (much slower than checking for a null value).
There is a third option. Instead of returning null, you can declare a NullPerson class as a subclass of Person and then return an instance of that.
Another case where null values are helpful is in object-relational mapping. Most relational databases allow for setting field values to “null”, which is distinct from an empty value.
December 11th, 2007 at 8:13 am
Fernando: I’m not saying there shouldn’t be something that offers the “functionality” of null, like nullable types, or a Maybe class. I’m just saying that it should be explicit. If a function returns a “Person” then the caller should be confident that he actually gets an object of the class “Person”. The function could be declared to return a “Person?”, though, or a “Maybe”, in which case the caller would know that the result might also be null, in which case it would have to do an explicit check for that case himself. The result: No more NullPointerExceptions, ever (and less null checks during run-time).
December 11th, 2007 at 12:10 pm
Thanks for all the comments, everyone! I appreciate hearing everyone’s opinions.
Daniel Lyons: Yes, indeed, that’s a big reason that Lisp multiple values work as they do. Extra values are just ignored. There were some other important reasons that they had to be retrofitted into Lisp that way, but this is considered one of the main benefits.
Jeremy: You say that CLASSPATH is “fundamental to the Class Loader model”. I would say that it’s fundamental to the model that there must be some basic class loader that just loads class files from where the compiler puts them, but it’s not fundamental that it has to have nearly as many problems as using an environment variable called CLASSPATH that just has a long string of directory and jar file names that isn’t error-checked, and so on. As a first step, one could make it more user-friendly. As a bigger step, class files could live in a DBMS and there could be some whole other way to express how they’re found. On the other hand, I’d be willing to be more critical than your later posting, and say that even people who have been using Java for some time still get into trouble with CLASSPATH although they’re more likely to recognize that that’s where the problem lies.
Jeremy and others: There are ways to make startup times shorter. Have these not been done because people aren’t using Java on the desktop, or is it the other way around, or both? I suspect it’s both! It’s the old “network effect”, also known as the “chicken and egg problem”. Eclipse’s startup time matters less than that of, say, the kind of thing you’d write a little PERL script for, since you rarely start it and it stays up for so long. I have not heard of the “Consumer JRE” project; can anyone tell us more?
Thanks for the pointers to the Java Service Wrapper, which I had never heard of!
Meneer R: No, what I’m saying can’t be used to defend C. I never even used the phrase “not used much in practice” so I don’t know what you’re objecting to! Maybe you refer to what I said about multiple inheritance, which I said was “not used all that often, but it’s not all that rare either”. The fact is, some things are more important than other things. So what do you so dislike about the Factory pattern? It’s just the way you do polymorphic constructors in Java. It’s a little funny that you have to use a convention (a new class called XXXFactory) but that’s a very small thing to learn, and then you’re all set. (I did say that many patterns are just to make up for things that got left out of the language.) Sure, without generics those objections were true, but that’s ancient history now. Ad hominem attacks on Java programmers are beside the point; I’m talking about the language itself. I completely don’t understand why you (correctly) associate type-safety with safety, but then say Java doesn’t have it! And I don’t say why you characterize Java as “too slow”, either, although the whole “which language is faster” issue is very hard to answer. It would be interesting to see many large production programs in ML/Haskell and see how that turns out.
Schani: You need “null” for the same reason Lisp uses NIL to terminate lists. Suppose you want to represent a balanced tree of Person objects. You need a “union” type that can be either Person or null in order to do it conveniently. That said, a type that could only be Person and not null would be a great thing. According to Prof. Michael Ernst of MIT, JSR-308, which will be added to Java 7, will allow “annotated types”, with which you’ll be able to easily have a “Person but not null” type. That will be a considerable improvement.
Fernando: I agree with Jeremy: if the name does not exist in the DBMS, you signal an exception. This is an “unusual result” case, as I will discuss in my future posting on exceptions. Nick Radov: I completely disagree about your first point. If you have to put in a check for null, that also alters flow of control: it’s the same thing. As for your second point, well, yeah, unfortunately, in an inner-loop situation, maybe you’d have to use the special-return-value style, if you think the not-found case will happen often enough to really affect overall application performance, but it’s generally bad programming practice, as I will argue in the future. Yes, I know many people disagree with me on that, but I hope to persuade you all. I’ll try to get to it soon. Schani: I don’t think having a Maybe class helps; you still have to put in a special check for it. As far as I’m concerned, you declare Fernando’s “findPerson” to return that new datatype that can only be a Person, not null, and then deal with the “not found” case using exceptions.
December 11th, 2007 at 12:31 pm
I wrote a little hack to fake mixins in java. Described (though titled poorly) here: Multiple Inheritance in Java.
December 11th, 2007 at 1:59 pm
dlweinreb: If you have non-nullable types then the type system guarantees that a Person is really a Person, so there is no need for a null check at run-time. Without non-nullable types you may still know that you really get a person, but the JIT might not. Of course, if you use a nullable Person, or a Maybe Person, you have to put in the check, but that won’t be the case nearly as often. Also note that you’ll have to check explicitly, i.e. you will have to handle the null case one way or another, instead of just letting the run-time throw an exception that’s not caught anywhere.
And no, you don’t need null to terminate a list. Here’s a generic List class that doesn’t use null (the syntax might not be completely correct because I’m not fluent in Java:
public class List<T> {
public abstract bool isNull ();
public abstract T hd ();
public abstract List<T> tl ();
public List<T> nil ()
{ return new Nil<T> (); }
public List<T> cons (T car, List<T> cdr)
{ return new Cons<T> (car, cdr); }
}
class Cons<T> : List<T> {
T car;
List<T> cdr;
Cons<T> (T _car, List<T> _cdr)
{ car = _car; cdr = _cdr; }
bool isNull () { return false; }
T hd () { return car; }
List<T> tl () { return cdr; }
}
class Nil<T> : List<T> {
Nil<T> () {}
bool isNull () { return true; }
T hd () { throw SomeException; }
List<T> tl () { throw SomeException; }
}
December 11th, 2007 at 3:09 pm
“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.”
public MainClass{
int variable = 1;
public MainClass(){
new InnerClass();
}
public void main(String [] args){
new MainClass();
}
static class InnerClass(){
public InnerClass(){
System.out.println(MainClass.this.variable);
}
}
}
December 11th, 2007 at 4:12 pm
Complaints I’m Seeing About Java
[...]At Object Design, I developed code in Java for over ten years, and I worked with Java more at BEA.[...]
December 11th, 2007 at 7:40 pm
I have to admit I’m shocked by your statement – “I’d be happy to use [Java] again.”
I discovered Java in early 1996 and switched to it from C++. I programmed primarily in Java from 1996 until 2006 when I discovered Ruby and switched. I have to say that because of the increase in joy and productivity I’ve experienced with Ruby, I would dread going back to Java (e.g. if I felt it necessary for financial reasons).
With your Lisp background, I’m amazed that you would be happy to use Java again. I suppose your verbosity tolerance may be higher than mine.
December 11th, 2007 at 10:09 pm
“Integer overflow is entirely silent. This is bad.”
I agree.
However, it just so happens that I was browsing HAKMEM recently, and ran across this from ITEM 154 (Gosper):
“If arithmetic overflow is a fatal error, some fascist pig with a read-only mind is trying to enforce machine independence. But the very ability to trap overflow is machine dependent.”
This is also the item in which it is “proved” that the universe runs on a twos-compliment machine.
What fun!
Good to see you writing.
- Tom Shepard
symbolics, 1983-1988
December 12th, 2007 at 1:53 am
Sure, you don’t literally need “null” to terminate a list, but you need some magic value to check for. Yes, in your example there aren’t any NullPointerExceptions. Instead there are SomeExceptions. Is that really so much better? I guess it could be a little bit better because the SomeException could be very specifically for List termination, but other than that I don’t see a big advantage.
December 12th, 2007 at 4:34 am
asd: I think you’ve detected a misstatement. It probably should have read “Variables declared in a method body and referred to by an inner/anonymous class declared within that method must be declared “final”.”
public class Test {
public void fail() {
int noGood = 5;
Object x = new Object () { public int hashCode() { return noGood; } };
}
}
yields: Test.java:4: local variable noGood is accessed from within inner class; needs to be declared final
December 12th, 2007 at 9:34 am
dlweinreb: So what you’re basically saying is that Java would be better off without static typing? Because this is, at heart, a static typing issue. The type system lets you pass a value which is not a string (null) to a method declared as expecting a string. When that method uses that value an exception is thrown. Why not just throw declared types completely overboard? Less overhead in the compiler, more possibilities, and what the hell if programs crash more often?
December 12th, 2007 at 10:05 am
schani: Of course not. You just have to remember that Java type “String” means “either an object that is an instanceof class String or null”. The fact that type “String” means that may be confusing but it’s well-defined and documented. If that method doesn’t check for a null, it has a bug and should be fixed. Meanwhile there ought to be some other type that means “an object that is an instanceof String”, and apparently that will come in Java 7, using these annotated types.
C and C++ are like this too (a pointer can be null and you have to check for it), and if memory serves Pascal and the languages based on it are also like this, and I think I’ve been told that Microsoft’s C# is like this too (perhaps someone could verify this for me?). Does anybody know a typed-variable language that has references/pointers in which ordinary declarations of reference/pointer types don’t allow something along the lines of a null pointer, and if so do they also have a type that does allow a null pointer?
December 12th, 2007 at 1:11 pm
First of all it’s irrelevant whether other similar languages have that bug, too. I already mentioned ML and Haskell as languages which don’t have that problem. Second, yes, of course it’s documented, but documenting dynamic typing doesn’t magically make it static typing.
And if you’re serious that a method which doesn’t check for null has a bug that should be fixed then please file bug reports for every method in the Java standard library which misbehaves for null arguments. I suggest you start with println.
Yes, I now it’s ridiculous – and that’s exactly my point! You aren’t supposed to pass null to some methods! I’m just saying that the type system should enforce that statically, instead of having to do it dynamically.
About C++ you’re wrong in a way, actually: Yes, C++ pointers can be NULL, but that’s natural because they’re, well, pointers, and you can do pointer arithmetic and all that stuff. C++ does have references, though, which cannot be NULL (unless you use trickery via pointers).
December 12th, 2007 at 5:01 pm
In other words, the ability exists, and there’s a way to get either behavior (through references which cannot be NULL, or through pointers which can be). Auto variables in C++ can’t be null, per se (aside from pointers on the stack), but I’ve seen many people use init() methods, meaning that a constructed object of that class isn’t useful until init() has been called. Kind of like a null.
For the record, I like C++. However, (1) I realize that the main way to program safely in C++ is through good conventions because some things are not enforced (and, BTW, I’m happy that some things aren’t enforced; typechecked exceptions make adding a new exception a big “all at once” change, while C++ allows a more gradual approach [with the understanding that if you throw an unexpected exception or an exception that violates the throws() declaration that your program will crash]), and (2) Java will get this ability in the near future (although it looks like the default behavior may be the wrong one).
December 12th, 2007 at 8:02 pm
My biggest gripe is attribute and method accessibility. Protected is not enough and private is too much. I would like to see something in between that will allow any sub-classes access but not classes within the same package.
I too often see the misuse of private methods that mean you need to re-implement large parts of an existing class to change the functionality of a single method. Some developers seem to use private, as the default, without thinking about the consequences. This may be fine for inhouse development, where the accessibility can be changed but when reusing other people code this can be a real pain.
December 13th, 2007 at 12:25 pm
Max: Very good point about the C++ references (the things starting with &). It’s been so long since I programmed in C++ that I forgot about those.
Schani: Well, we see it different ways. If some method responds to getting a null pointer by signalling NullPointerException, that’s probably not a bug in the library: that’s probably exactly what the library method is contractually obligated to do. So there is no bug report to be filed. However, many library methods probably would have been better had the “no nulls allowed” type existed and had they been written with that type. Too bad they didn’t put that type in from the beginning. Yes, it should be enforced statically, we agree completely there. I don’t think the analogy does not apply to C++ merely because you can do pointer arithmetic in C++. (You can’t do pointer arithmetic with null pointers, can you?) But you’re right too about the C++ reference types, same as Max.
December 13th, 2007 at 2:01 pm
Thinking more about it, what is the proposed solution to Java’s “null reference problem”? Throw an exception when the object can’t be created instead of returning null. Why is it a problem? Because if you forget to check for null, Java will throw a NullPointerException.
I’m really having a hard time figuring out how the solution improves things.
December 13th, 2007 at 9:09 pm
dlweinreb: So we actually do agree!
And sure, you can do arithmetic with null pointers. It’s common practice to do something like this, for example, if you want to get the offset of a struct field if you don’t have offsetof (i.e. if you have to implement it yourself):
#define OFFSETOF(s,f) ((size_t)(&((((s)*)NULL)->(f))))
Basically, you pretend that you have a struct of that type at address 0, then you get a pointer to the field you’re interested in and cast that pointer to an integer.
C and C++ have added some simple syntactic sugar for the null pointer because it’s convenient, but even if they didn’t, there’d still be a null pointer, just like there is a seventeen pointer ((void*)17).
Of course some of that is not strict ANSI/ISO C/C++, but if you go by that standard, there probably wouldn’t be any programs left, except maybe some very carefully written Hello Worlds…
December 13th, 2007 at 10:57 pm
I first wrote a lengthy reply is comment to the complainers.
But I asked myself, if I were to use a web site or an application written in a language which had or didn’t not have any of these feature would I notice the difference?
In the few cases which it might be possible, I would suggest the developer made a bad choice in selecting an int when she should have used a long or a BigInteger. This is not the languages fault.
Perhaps the problem is that Java isn’t as cool as Flex, Ruby or Groovy, but perhaps it shouldn’t be try to be because it isn’t any of those languages.
BTW: IntelliJ supports static and dynamic checking of null arguments, fields and return values. It can even tell you if you have checked for null when the value cannot be null.
December 13th, 2007 at 11:48 pm
About the null object issue: Suppose I have a variable that gets declared in one place, and possibly set (or not) later, depending on how things go. Then I pass the variable as input to some method. Therefore, the compiler simply cannot know until runtime whether the variable refers to an object or not.
Perhaps the variable always gets set first, somehow, but the compiler lacks the intelligence to prove this. (If it could do so, then I could use the compiler to solve the Halting Problem.)
The reason this is not a problem in functional languages is the functional languages have no variables — i.e., they don’t have references that are created before the object to which they refer. In purely functional languages, the reference is created when the thing referred to is created, and never earlier.
Java’s inability to prevent null-value exceptions is unavoidable due to its inclusion of imperative language capabilities. If you don’t like imperative languages, fine, but once you accept that Java is an imperative language, the null-value issue becomes an unfair criticism.
Of course, one might have defined Java with a purely functional subset, and with special non-nullable types which can be created and used only within that purely functional subset. But then Java would be a completely different kind of language. Perhaps functional language specialists have already created a language of this kind — one in which the use of non-functional features is segregated from the functional parts, much as C++.NET separates managed from unmanaged code. I wouldn’t know; it’s been over a decade since I’ve been involved in functional programming language R&D.
December 14th, 2007 at 10:30 am
Frank: What you’re talking about is a non-issue. You can demand that a variable will always be initialized and still permit changing it later. Those are orthogonal issues. If they were not, then Java would be a functional programming language when it comes to integers, for example – you cannot declare an integer in Java without initializing it (not writing an explicit initialization is just syntactic sugar for initializing it to the valid integer value 0). I could also mention (again) that C++ references are non-nullable, and C++ is still very much imperative, even if you always use references instead of pointers.
But to give you an idea of how to accomplish this in Java without solving the halting problem or making it a functional language, here’s how I’d do it:
First, observe that there are three different live ranges for variables: variables local to a method, instance variables and static variables. Here’s how to initialize them:
Local variables: Always have to be initialized at declaration.
Instance variables: Have to be initialized in every constructor at top-level, and must not be used in the constructors prior to initialization.
Static variables: Two possibilities: Either demand that they are always initialized in-line, or, better, introduce static constructors like C# has and demand that they are initialized there, just like instance variables are initialized in object constructors.
Moreover, if you really do need a variable that is null-able, then you could still use another mechanism. I would include either nullable types (which act exactly like the object types in Java do now, i.e. they are nullable) or a Maybe class in the language/runtime. It’s a simple issue and you wouldn’t lose anything.
And if you still don’t believe me, then I challenge you to provide an example of a Java program that cannot easily be rewritten without using null (assuming my criteria for initialization above).
December 14th, 2007 at 2:38 pm
I disagree. This isn’t the Halting problem, but simply a question of code path analysis. Unless the developer is writing:
Object o;
boolean b;
…
o = b ? new Object() : null;
…
And if b depends on something at runtime, then you’re right that you can’t tell if o will be properly initialized. But if (1) b is a compile-time constant, or (2) explicitly setting an object to null (or at least writing code that may intentionally set an object to null) is either illegal or a hint to the compiler that you’re willing to accept NullPointerExceptions, then it is possible to prove if o is properly initialized by walking all code paths (it’s probably inefficient, but it is possible).
You may have heard about the SSA optimization that relies on this same technique. SSA exists today and did not need to solve the Halting problem.
However, for the record, I don’t think null must be killed. Null is a useful concept in some cases. It may be nice to change the default behavior, but it’s much to late for that now.
December 14th, 2007 at 2:39 pm
Aargh. People will probably figure it out, but I should have changed that “Unless” to “If,” in “Unless the developer is writing …”
December 14th, 2007 at 3:24 pm
As was already pointed out, if your language demands that the variable be instantiated when declared, then all you’re doing is replacing a null-value exception with an application-level “unexpected default-value” error.
No, code analysis cannot _always_ determine whether a variable has been initialized. If initialization depends upon the termination of a method call, then proving initialization is equivalent to proving termination. (Of course, you could make it an error if the compiler is unable to prove initialization, even though it will be initialized. That would force you to initialize it with a useless dummy value; see above.)
December 14th, 2007 at 8:51 pm
Daniel Weinreb, a MIT graduate, a man who co-founded Symbolics, one with significant contributions in the design of the Common Lisp language, a co-author of CLtL, would be HAPPY TO USE IT (JAVA) AGAIN?! Man, you’d make a great title of a Java blog. Actually, no – you deserve more! Your article would fit best in the Developer Spotlight on java.sun.com (I just was there).
By the way if I didn’t dig beneath your words, I’d have never believed you. You praise a limited language with poor expressing abilities while you are an engineer in a CL project.
I suppose that people with your level of experience are mostly involved in high level design and architecture decisions. So your definition of “use” is much, much different from the general definition of the verb. If doing design and architecture is what you call “use” perhaps you are sincere. Especially when adding the additional advantage of the fact that it is comparatively easy to find good enough Java programmers. I doubt your use of the Java language constitutes in hunting for NullPointerException-s. However, I don’t think most of the people who read your article would realize that and I wonder if that’s unintentional fooling?
Java is a mainstream language, whose design has been mostly driven by marketing forces. We both know marketing is interested in people as a mass and not in the exceptional individuals, neither in those who are above the average.
The pervasiveness of languages like Java devaluates of the programmer’s profession. Without matter how talanted a developer is – he is stuck in dealing with verbose, static code.
I’ve contemplated commenting on this article for quite a while. Sorry I couldn’t resist.
OK, back to my VB project
December 14th, 2007 at 10:00 pm
Frank Silbermann, thank you for pointing out what I was missing.
December 14th, 2007 at 11:44 pm
Frank: I cannot add to what I’ve already said. It wouldn’t be an issue in practice. Again, I challenge you to show me a reasonably small Java program (because my time is limited) which cannot be rewritten without null and I’ll buy you a beer
December 15th, 2007 at 3:45 am
schani: Sometimes in a Java program you want to declare a variable, but then you need to do an “if” statement to go down one of two paths, each of which will initialize the variable differently. In the meantime it’s convenient for it to be null, since there isn’t anything else good for it to be, and there’s no point in creating some dumb object to be its value.
This also comes up when you need to declare a variable before a “try” block and then create the object inside the “try” block, a very common pattern.
Looking at some of my own code, I see that I have object-type instance variables whose initial value can’t be created until a bunch of other computation has been done.
I doubt that there’s a program that cannot possibly be rewritten without nulls; the issue is whether the nulls are often useful for natural uses of the language.
Kamen Tomov: No, by “use” I really do mean program in it. And I’ve spent many years programming in Java. I’m no longer nearly as much of a language loyalist as I used to be. I think I explained myself in the first paragraph of the post. Saying that “its design has been driven by marketing forces” is sort of like an ad hominem attack. Surely you would not say that language X must be bad because the guy who designed it had a funny-sounding name or came from the West Coast, right? Java is a defined language. We don’t have to worry about where it came from or why in order to judge its pros and cons as a language, and to notice what libraries and tools are available for it.
The main point I wanted to make is that I didn’t write this posting because I hate or want to defame Java.
That said, I’m using Common Lisp these days and loving it. And I refuse to ever use C++ again.
December 16th, 2007 at 7:17 pm
How are the marketing directives irrelevant for software design considerations when it comes to commercial prodcuts? Is it just because I didn’t elaborate on “marketing forces” that feels like an ad hominem attack? Doesn’t it rather mean that I just didn’t find a necessity/time/will to do so? Anyway, I decided to face the challenge and elaborate. You can find my analysis here.
Apart from that who else if not people like you should demonstrate loyalty to CL?! Also if not for loyalty why not do it for the superiority of the language?!
In fact the desire for experiencing some known and used technology is a feeling that I know well. It is when one wants to recall the good old feeling of it. It probably has to do with the quality of the human mind to forget the bad.
January 3rd, 2008 at 11:26 pm
Hi Dan, I’m enjoying your blog.
On a couple of the less-debated topics from this posting:
Finalizers: Java finalizers are nothing but trouble. Relying on them for resource cleanup is asking for a leak, since you have no idea when the finalizer will be run. Another problem is probably limited to particular JVMs. In 2005 or 2006, my company was using a recent IBM JVM on Linux. We do extensive stress-testing, and we would sometimes see OutOfMemoryError, after running without problem for many hours or days. This seemed to be due to the JVM having one finalizer thread, which would fall behind. We eliminated finalizers from the classes in question, (or rewrote code to avoid JDK or 3rd party classes with finalizers), and that fixed the problem.
On multi-value returns, collection literals, and brevity: I also wish that Java had a multi-value return. I find it extremely useful in Python. I could always write a little Pair-like type in either language, but that’s overkill, especially in Java. Being able to return a Pythonic tuple is just the right thing. This relates to issue of collection literals and brevity in general. Python’s syntax for writing tuple, list and dict (map) literals is very, very nice. I think it is one reason for the perception that Python is more concise than Java. [1, 2, 3] is a lot better than “List list = new ArrayList(); list.add(1); list.add(2); list.add(3);” (Arrays.asList(1, 2, 3) is a little better.) In general, I think that Java would benefit a lot from stealing some syntactic ideas from Python, including multi-line strings and maybe even string formatting (Python’s %).
January 5th, 2008 at 3:49 am
Concerning important library classes being subclassable because they are declared final: there’s a simple fix fo this which the library implementors should do. The complete implementation should be provided in some non-final class which others are free to subclass. The library can then finalize and make common use of subclasses of these implementation classes. Anyone who considers declaring a class final should do this.
May 16th, 2008 at 12:24 pm
[...] Complaints I’m Seeing About Java [...]