vuurrobins devblog

A place to dump my development stuff.

I have been working with java for quite some time now, and the more I look at it, the more I dislike it. It has some good design decisions that it implements well, it has some good design decisions that it just didn’t implement well, and there are some design decisions that just made me go WTF. Now I know that some of those decisions are because they thought that it was the best way back when they created java (the language is 15 years old, being first released in 1995) or trying to make the language simple and idiot proof or that the decision was based on another, older decision to keep compatibility between java versions. But that doesn’t mean that the decisions aren’t bad. So here are 7 design decisions that I think are either bad or badly implemented.

Note that I only list things of the core language here, not of the standard library, frameworks/libraries, documentation, virtual machine, etc. I also try to be objective and put only actual decisions here without going into a rant. Although I don’t explicitly say what it should be, I think that it is fairly obvious to tell what needs to be changed to improve the language (although that would change the language to much). I also make a lot of comparisons with c++ because I have the most experience in it and I like the most.

One final thing before we go to the list. This is just my opinion. If you disagree with me and you want to discuss it, then go ahead, but do it in a respectable fashion or go away. There are a lot of other people’s opinions that you can read.

On to the list:

1. Final doesn’t make an object final, but only the reference final.
With c/c++ you have const to say that a variable shouldn’t change, and with java you have final to say that. Or at least it’s supposed to do that. However, putting the final keyword in front of a declaration of an object doesn’t prevent that someone modifies the object through the reference. It merely prevents someone to change the reference so that it points to another object. To put it in code:

[code]
final ArrayList mylist = new ArrayList();
mylist.add(new Integer(20)); //THIS WORKS
mylist = new ArrayList(); //THIS IS PREVENTED
[/code]

There is absolutely no way to prevent someone from modifying the object through mylist, although you would expect that it can’t be modified. Why they decided to not have constant/final objects is beyond me, but I think it is a bad design decision.

2. Every piece of code must be in a class.
Java is object orientated, I get that. In order to create objects, you need classes to create them from, I get that. But what if you don’t want to create an object, what if you just have some code that doesn’t belong with an object. What if you have a function that just calculates something without having a state? What if you have code that doesn’t belong in a class? Well, tough luck. You need to put it in a class.

Seeing as your function doesn’t have a state, it should be static so you don’t need an object to call it. Some IDEs then tell you that since the class doesn’t have any attributes, that the class should be final, so you can’t extend from it and that it should have a private constructor, so you can’t create objects out of it. What kind of purpose does the class have then, other than acting like a weird form of a c++ namespace? The class doesn’t add anything, its more work and it’s confusing.

What’s even worse is that it’s not uncommon that java programmers don’t make there function static, meaning that you have to create an object just to call the function. But that’s an error of the programmer, not the language (although the language doesn’t help with it).

I think that they decided this in a misguided sense that putting code in a class means that the code is more object orientated, but its not. In fact, it actually hurts OOP because you end up with a bunch of meaningless classes that doesn’t serve its purpose of creating objects. So I think it is a bad design decision.

3. Every collection implements collection interface, except array.
This is an example of a good design decision that’s been implemented badly. A collection is a type that holds a number of variables/objects of any given other type. Because there are so many different ways to implement a collection (array, linked list, map, set, tree etc) they decided to create a collection interface and let every collection implement it. Except probably the most basic one, array. The collection interface was created to insure that every collection had some base methods that you could use with every collection. What is the purpose of it if the most basic type of collection doesn’t implement it?

Another problem is that when they created the collection interface, the language didn’t have any support for generics yet. So they abused polymorphism and made the collection a collection of objects, because every class implicitly extended the object class. This however means that collections that implement the collection interface cannot contain raw data types like int (not even if you use the generic version of it). And because array is some kind of half raw data type/half class, it can use raw data types like int. WTF.

If they used generics from the start, or rewrote it when they do used generics, they could let collections take any kind of data type, and they could let array implement the collection interface. But they decided not to change it, which I think is a bad design decision.

4. Your program is a bunch of class files, not 1 single program file.
With c++ you compile your code into object files and then link your object files into 1 program. With java you compile your code into class files and … that’s it. Your program consists of a bunch of class files that you can rename, replace or remove, which all will break your program. It’s your responsibility to keep everything together. They even went so far to include a NoClassDefFoundException in the case it can’t find the classes file. You can even catch the exception and continue the program if a part of your program is missing. If you ask me, you shouldn’t even be able to start the program if you miss a part of it.

Another problem is that the classes need to remember the name of the class it wants to access, the name of the methods it wants to call and of course its own class and methods names. If you have 30 classes with each class having 10 methods, then you have 30 class names and 300 method names that all take up unneeded space. You can say that having all those names take up very little space, but not having those names take up even less space. The computer doesn’t need them, so why keep them.

What’s even worse is that the compiler can’t even optimise the program. It can’t remove dead code, it can’t inline small methods, it can’t replace constants from another class with the actual value (the compiler doesn’t even know it’s a constant during compiling). This and the fact that it needs to search for files and classes during runtime causes the program to be unnecessary big and slow.

And no, putting your program in a *.jar file doesn’t help either. It may help keeping your files together, but any other archive file like *.zip or *.rar will also do that. You can set a class file that it should try to execute so the user doesn’t have to choose it, and that is probably the only advantage it has. The rest still applies, no optimising during compiling, no checking if all the needed files are present (you can even leave files out when creating the jar file, or just forget to put them in), the files still need to be searched and loaded at runtime etc. You can even unpack the *.jar file to get the class files back.

All of this is very error prone, makes the programs big and the language very slow. Maybe they thought that compiling time should be as small as possible (it should, but not at the cost of runtime) or maybe they had some wild idea about swapping class files at runtime. But whatever reason they had, I think it is a bad design decision.

5. No operator overloading
This may not look like a big point, but sooner or later you will miss this. Operator overloading allows us to use object in a way that we have known since we were little, quickly grasping what’s being done to an object without looking up what and method does. Sure, most classes don’t need it, and it shouldn’t be overused or abused. But when used right, it can make code look good, be easier to create code and easier to understand code.

One of the places where it would be useful is with compareTo. Currently you use the Comparable interface and implement its CompareTo method to compare your object with another object (or data type). If you can overload operators, you could overload the == operator and compare object the way you would do with raw data types, which is more consistent.

They probably thought that it wasn’t needed, or that it was too difficult, or maybe they just didn’t think of it. But they didn’t include it in the language, which I think is a bad design decision.

6. Raw data types and class versions of it.
This is a bad design decision based on another bad design decision. They thought that in order to keep the language efficient, they had to include raw data types that were allocated on the stack instead of the heap. However, because of some limitations of the raw data types (like not being able to use raw data types with collections), they at some point created class versions of the data types, meaning you have for instance an int data type and an Integer class. There are some differences between them, like ints go to the stack and Integers go to the heap, and Integers has methods and ints don’t. But for the most times, they act completely the same. You can cast an int to an Integer, or assign one to another. They can both use operators like + and – (yes, Integer is one of the few classes that has operators, although you can’t give your own classes operators) and you can pass Integers where ints are expected, and vice versa.

I seriously wonder why they created a second version of the raw data types instead of joining them, or fixed the flaws of the raw data types. It can only be confusing, the differences aren’t that big (especially if they change the other bad design decisions I mention here) and it would be easier and faster to write code. But they decided to create a separate class version of the raw data types, which I think is a bad design decision.

7. No control about in which memory you create variables/objects.
This is one of the things that annoy me the most. If you have a local/temporary/worker variables with c/c++ (and other languages), you put in on the stack. Putting it on the stack means it’s faster, smaller and it gets automatically freed in a way that’s faster and cleaner than dynamic memory ever will be (both manually freeing it as well as using a system like a garbage collector). Likewise, if I have variables that are very big or should live longer than the local function, I would put it on the heap. It may be a bit bigger and slower, but it is great for large, long living variables. With java, you can’t choose where you put your variables, it chooses for you.

With java, every object you create with ‘new’ is put on the heap and every raw data type variable and references to objects (yes java has references) are put on the stack. So if you quickly need to calculate something or temporarily need to store something using an object, you have to put it on the heap. This means allocating memory from the heap (and throw an error if there isn’t enough memory), use the memory through the reference(s) and collect the memory if there are no references left. And that while you just want to use the object inside 1 function for a few lines. If this happens a lot (and trust me, it will) your program will be very slow, or at least a lot slower as it could be.

Note that I’m not talking about the garbage collector. The garbage collector frees unused memory on the heap, which is a good thing considdering java’s goal. I’m talking about that you can’t put your object in the heap in the first place.

One instance where you create an object that should be on the stack is when you have an instance of a class that implements the Runnable interface, and you want to start that instance. To start it, you need to create a Thread object using your object as a parameter, and then call the start method of the Thread instance. This usually comes down to the following code (assuming your object reference is called runMe):

[code]
new Thread(runMe).start();
[/code]

The Thread instance is created and used in a single line, and isn’t needed beyond that. The stack would be the perfect place to put the Thread instance, but java puts it in the heap because java thinks it knows better than me or thinks that I can’t make that decision. Okay, okay, I’ll get out of the rant. But still, this is a design decision where they sacrificed performance for simplicity, which IMO isn’t needed and thus a bad design decision.

There you have it. 7 java Design decisions that I think that are bad. I think that a lot of decisions are to make java simple and idiot-proof, sacrificing performance as well as programmer competence, causing less stable code. I hope that someone will find this useful to learn more about how java works, what are some of its pitfalls and what you need to add or avoid when creating, comparing etc a language like java.

Thats it for this time…

Tagged with .

Comments are closed.