This is not an Anti-Java rant Per Se. It is a rant about the two main things missing from the language that force people into code heavy work-arounds.
Java has two flaws that hurt programmers using the language. The first is that the reflection API does not provide the parameter names for a function. The second is that Java allows null pointers. This article explains why these two flaws are the impetus for many of the workarounds that require a lot of coding to do simple things. This added complexity in turn leads to code that is harder to maintain and less performant.
An object in Java does not have any of the features of many true “Object Oriented” programming languages. You can’t add properties or methods to an object after it has been created. You need another abstraction for that kind of stuff: the map. But Java provides introspection of the objects that make them map like. An Object in Java is the “realization” of a Class, which is a set of rules. The class exists to allow the programmer to define new rules about what a set of objects will do. The idea is that the Class is the primary abstraction available to the programmer. An Object has pre-conditions and post-conditions for any operations: this will be true before and after this method is called. These invariants are enforced by the Class of the Object.
This is the theory. In practice, most Java classes violate this. Java has one part of the problem built in to the language design of Garbage collection. In C++ it is pretty common for an object to represent a resource. Create the object, allocate the resource. Free the object, release the resource. But garbage collection comes with a price: you don’t know when your objects are freed, which means you can’t tie your resources to their object lifespans. This is unfortunate, but it is a limitation of the language with which I can live.
However, just because we can’t tie clean up with resource release doesn’t mean we should allocate invalid objects. However, this is done all over the place. Lets look at the dependency injection model called setter injection. Create an object, using the null constructor, and then call set, set ,set, and when you are done, you have an initialized object. Note that type 1, or interface injection, is really just a more type safe way to do the same thing. There is no way of telling what is the minimum amount of work we have to do to get a valid object. Do we have to call set on all properties? The language already has a mechanism for answering this question. THat is what the constructor is supposed to do. Type 3 injection, constructor injection, then, looks like it should be the default way to go. Why is it then so underused?
Imagine a language that gave you map, but no way to use the key. You could enumerate through all of the values, check their types, do all sorts of cool things, but you couldn’t look up values from the key. Programmers would probably complain? Yet the introspection of parameters in a java.lang.reflect.Method is limited to Types, not the names themselves. The same is true of a java.lang.reflect.Constructor object. We can get a collection of types, even a collection of annotations, but not a simple collection of strings for the names. even if we did, there would be no way to create match that value with the object passed in as the parameter.
Assume that you want to create an object of type DatabaseConnection. To create this, you need a user ID, a password, and a JDBC URL. Three strings. To those of you who use objects like this regularly, you’ll notice that I changed the order. The JDBC API usually has it as URL, Uid Password. If all you know is that your API takes three strings, what order do you put them in? You have to read the API docs. Which is really not that useful if we want to make this an automated process. Ideally, the names of the parameters in the constructor would tell us which is which.
Note that if we used a specific type for UID, Password, and URL, we would have a guaranteed solution: match the types of the parameters with the types of the objects that fill the dependencies. But as soon as you have two objects of the same type, or any amount of casting, the policy becomes non-deterministic.
C++ aside: C++ suffers from this just as much as Java, but C++ doesn’t even pretend to provide as much run time introspection that the failure there is just as bad. Interesting to note that in modern compilers, C, and by extension, C++ has allowed named parameters for structures, which can be used for this type of introspection, albeit a very chatty and non-runtime type. Any one that suggest trying to demangle C++ functions will quickly see that A) any solution is non-portable and B) you lose the parameter names anyway.
Java has one other critical failing. Null pointers. If Java required that all references had a valid object connected to it, most of the justification for the Bean API would fall away. If we defaulted most properties to final, the majority of objects would be immutable, and a whole slew of concurrency exceptions would fall away. We would then just have to deal with the cases that an property was supposed to always exist, but be mutable. This is why we have classes in the first case, and so these types of classes would be more common: Wrap a primitive, but provide additional rules about what values it can assume. Without Null pointers, there would be no need for the Bean API.
Note that it would be easy to simulate a null pointer using a collection, or even an iterator. A Collection would be empty. An iterator would throw an exception to indicate that there was no “next” object. This kind of Null pointer exception would be the exception, not the rule.
These two rules: “no null objects” and “parameter type info” would significantly reduce the quantity of code written in Java while increasing reliability and correctness.