The Java Programming Language really doesn't have much going for it, apart from having been a favourite language for use in academic environments (in particular for undergraduate teaching) over the past decade or so. Having been forced to use this a few times for a few projects, I have to say that it has a few really rather-inexcusable traits for most practical usage.
From the user side
For many years, I already had a more than mild distaste of Java-based software. Perhaps it was from a combination of poorly-written code, old/crappy hardware, and old Java VM's running, but it was always a bad sign when trying to run anything written in Java back in those days. Such programs were characterised by the following traits (quite a few of which classify them under the criteria here):
1) Very slow to load - it would always take a long time for even a splash screen to come up (5-10 seconds), followed by a long wait on the (nearly obligatory) splash screen which would typically take twice as long to finish loading everything else.
2) Terrible responsiveness - In addition to slow loading, redrawing UI elements following changes would take a while, usually characterised by blank rectangles appearing that took 2-5 seconds to get filled by content again. This includes the main windows taking similar amounts of time to draw on startup. This can get infuriating after a while.
3) Memory hogging - The Java Heap would end up taking up a large amount of space that it "might" use, though usually didn't. This often meant that other stuff ended up getting swapped out (contributing to 1 and 2).
4) Garbage collection slow and frequently locks up application - As Java practically makes hand-controlled memory management very difficult, it comes with a "Garbage Collector", which is designed to take care of all of this for the coders. This allows heap memory to be reused for new memory-requirements within a program, once some existing allocations are no longer needed. However, to do this, it must run from time to time, usually when it decides that the system is "idle". As it so happens, this often happens when the user is just about to click a button to do something else. During GC, the GUI is unresponsive to input, as the VM is totally consumed with performing GC-related tasks, which ends up contributing to 2.
5) Reliance on a VM to run code "interpreted" - this always felt a bit wimpy, that the code couldn't stand by itself without hoardes of reinforcements. Then again, I've since backed down on this stance since using Python (which is purely interpreted), but then Python doesn't really have such a heavy set of libs/dependencies.
As a result, there were a few years when I actively avoided touching anything written in Java, as 1 and 3 would be enough to make my computer at the time completely unusable until the app was up and running.
However, while working with Java, I have come to understand that the slow loading was very likely due to the "compiling to native code caches" step that the VMs apparently do on first startup, which sadly seemed to happen everytime the programs were run (caches were either constantly replaced, or defective). Also, the other factors have largely been less of an issue when I've worked with Java stuff recently, though if NetBeans is anything to go by, 2 probably still holds when working with "larger" products.
From the coder side
Now, let's look at Java from the other side of the fence. Trying to maintain an optimistic mindset to learning the language, some things become apparent.
Let's start from some "relatively good" aspects about it (some of which were not totally obvious until encountering the horrors of C++, which I'll leave for another post):
1) Collections API - This by and large makes pretty good sense, and is well designed. It is however tainted by a few areas of ugliness...
2) An acceptable "string" type/library - This is compared to the shambles seen in C++ (stl, not boost, but still) where it was just a jumble of several different types, obfuscuated and backwards method signatures laden with templating crap, and lack of easy support for some common tasks. However, IMO nothing beats Python strings (and that includes the obfuscuation + mistaken "power" you get from trying to use "regrex")
3) Automatic initialisation of variables to 0
4) Consistently defined types and order of operators
5) Overall greater simplicity in handling method overriding than C++, as in C++ you'll practically end up having to make many methods "virtual" to support good extensibility.
6) Built-in GUI toolkit that is semi-decent. I do have a few gripes about some aspects of it, but compared to a few other toolkits, its Tree-Widget implementation is the most flexible/logical I've seen, as it does a proper MVC separation.
However, this is really where it all ends. From here, we begin to look at some of the things that make it less than elegant.
1) One of the problems with the Collections API is the use of "generics" (Java's attempt at "templates" as in C++). While this concept on the surface offers a lot of promise, it has really been quite a massive failure in the "production languages" used on most software (C, C++, Java). As anyone who has used these will find, they usually end up being annoying to get working, cause rather opaque error messages, and (especially in Java) need specific compiler warnings to be suppressed manually by the coder to get the only correct functioning form of code required to compile/work. Personally, I much prefer the relative freedom offered by simple C "macros", which are really just simple text-replacement hacks, but which are practically sufficient for the purpose they serve (despite some vocal opponents)
2) add/remove methods on iterators are something that I really feel is quite icky. Taking some care over this yourself is the true way IMO.
3) Lack of proper operator overloading. C++ and Python both offer this, but sadly Java doesn't. It really feels like a lost opportunity when trying to hack up new numeric types, especially when methods can be overloaded readily.
4) Weirdo "anonymous class" syntax. It is interesting in terms of what it lets you do, but seeing it for the first time (or before you understand how they can possibly work) is just plain weird.
5) "Primitive data types". Perhaps the designers of Java realised that having everything as objects was too much bloat. But having this distinction is just asking for trouble...
What Java lacks in elegance pales in comparison to the irritation factor when actually trying to code with it. From experiences trying to get code compiling for quicky testing/experiments, the compiler can be a real pedantic PITA. Here are some of my pet hates, which really make using this crap quite unbearable...
1) Strict enforcement of conditionals having to evaluate to booleans (i.e. if (a != null) ... vs if (a) ...). Just because you have booleans, it doesn't mean that you have to make everyone use them. Specifically, for most people used to just about every other language out there, 0 = False and everything else is True. That is a given, commonly used, accepted, and understood convention (or "language feature"). Hence, every programmer worth his grains of salt in such languages will learn to recognise and use it in his own work. For the pedantic buggers who insist on always doing the "technically correct" comparisons to get booleans, I think they're incompetent coders. Furthermore, once you learn these conventions, extra typing explicitly spelling out these conditionals is actually pointless as it wastes time for competent coder+code-reader as well as makes some code unnecessarily long+bloated+clumsy to read.
2) Compiler error on "unreachable code". This case typically occurs when you're debugging, and you need to quickly get a large swathe of code out of action. Typically in most other languages, you just insert a "return" statement before the code you need commented out (if most of the function will be useless), put that code inside an "if (false) { ... }" (if the first case can't apply), or change a conditional to always run by commenting out the proper conditional (if you need to test one particular case only for validity), so that you can debug whether other code is working correctly under "semi-controlled" circumstances. However, the Java compiler goes all pedantic and "protect the users from themselves" (this is even written into the spec for a "complient" Java compiler), and refuses to compile that code. Seriously, it's not very often that a competent coder would really end up shooting himself with code that causes unreachable code like this that it should be worth stopping compilation over. At most, this is worth a warning, and even then, I've got some bones to pick with the "no dead code" crowd quite a lot of the time. Unfortunately, the only really feasible way is often to go in and go commenting out code line by line, which takes time and was what we were trying to avoid having to do.
3) Checked exceptions. Exceptions are only semi-successful IMO, but forcing users to "handle" exceptions is just nasty. While in theory it should help force coders to take care of when problems might occur, and provide fixes for these thus resulting in better code, the reality is that coders can still create very confusing code that doesn't actually properly deal with these problems. Furthermore, it means that it takes longer to get functioning code to test/improve, which ultimately is not a good thing (and indicates that the language is getting in the way of productivity).
4) Need to create special wrapper-classes to have "multiple" returns from functions. I've seen it argued that in a "true OO" way, you should be doing things like that if you're going to have to return sets of data. However, this is a pain for one-off functions (you end up having to code heaps of little helper classes for doing this). Furthermore, when you start thinking of the number of little instances of such helpers floating around, duplicating data that you're likely to copy off somewhere else again pretty soon, it really feels quite wasteful.
5) One class per file (not counting anonymous or inner classes), with the file needing to be named the same way. I can understand that this was intended to prevent coders putting 20+ classes into a 500kb single file. However, most of the time, any such cases are only temporary - that is, you just need to code a few classes in a single file for convenience, and they will get split off later as you find navigating the file a bit cumbersome. Furthermore, the filename=classname restriction is a bit of a pain when working with version-control, as it means that you have to plan your class renaming more carefully by splitting the rename into steps: commit all other changes, rename file and commit, rename class and commit, fix other references and commit, etc. which probably ends up breaking the buildprocess for everybody else in the meantime (unless working with branches, which come with their own downsides).
6) Printing text to stdout is clumsy (i.e. System.out.println(...); vs printf(...);). For debugging prints, which is a common use of this sort of printing, typing out that long line of glut is a hassle. Sure, it's organised "nicely" (in an OO-kind of way, rather than being a language feature), but it makes things so much clunkier. Let's face it: if they could be bothered incorporating a shorthand for working with Collections Iterators, surely they could have done one for this, which is equally if not more frequently used.
7) All code must exist inside classes - "Forcing" people to embrace OO doesn't mean that they're going to necessarily like it or produce nice code with it. Namely, coders frequently end up embedding a main() method within the single class their program has, and then initialise an instance of that very class from that main method and enter some kind of feedback loop controlled by the instance, to make their code all work.
8) Too many access modifiers - seriously, I think we really only need "public" and "private", with really obvious meanings, and just do away with the "protected" and/or "package" crap. In a way, it's nicer how C++ separates these into blocks (though the syntax is kindof vile like gotos) since you're naturally going to do that with a set of fields anyway. Then again, C++ has heaps more modifiers that have to be used far more frequently everywhere else...
9) No preprocessor = no macros - Sure, there's nothing stopping you from hacking a special buildprocess that runs the C-preprocessor on your sources and then pipe those through to the java compiler, but macros are really not evil if used correctly, and can really help simplify up quite a bit of code. Just because they introduced "generics" to try and fulfill the role they percieved macros as being used for mainly, it doesn't mean that they are totally useless still.
10) "Recommended" style for Java code is ugly - the style seen in many "official" pieces of Java code are often littered with caterpillar-ifs, great wads of Javadoc for 2-line functions, and other obscenities.
11) Creating enumerations (and actually using them) is a pain - When I last tried doing this, it ended up requiring so much extra code, that it was really not worth trying to use them. Instead, general/ungrouped "static constants" rule the nest.
I started using Java last Winter for school work, and I similarly dislike it. You pretty much nailed every one of my reasons for not liking it.
ReplyDeleteI've also found that I don't really have a need for Java. If I want power/flexibility I'll use C or C++. If I want to quickly develop something (or I don't need speed), I'll use Python.
However, I do like C++. :°
What annoys me most about Java (Having never written anything useful in it) is that it doesn't seem to give the programmer control like C. It's like working in a padded environment so that you never get hurt.
ReplyDeletewell, you definitely got something wrong, Java IS interpreted as well as compiled.
ReplyDeleteI don't know where to begin to refute your idiotic post.
ReplyDeleteJava heap ends up taking too much space and because of which other programs are swapped out? What? Java Heap space is limited to the amount of memory set. It starts with minimum specified and takes more if it is needed, upto the max specified. If your app uses so much memory that it feels up your entire heap, you need to learn about memory leaks and how to free up objects.
I work in a stock exchange and our entire code base is java. We have microseconds to process an order and we do all fine with java.
Become an expert in something before dissing it. You just seem like ignorant idiot with your rambling.
Re Anonymous:
ReplyDeletePerhaps a bit of additional context is necessary here to clarify some points.
This rant was posted several years ago. As far as the performance complaints mentioned go, yes, these aren't really a problem anymore today.
However, the issues mentioned were quite a big drawback around 2003-7 (?) on the desktop I was running back then with the following specs:
- 224 mb RAM available for OS + apps to run on
- System would start swapping when around 120-150 mb was used
- Crappy integrated S3 graphics card, complete with a whole host of driver issues (e.g. minimising an OpenGL window could be enough to crash the associated app, but not before corrupting the display)
- 1.1 GHz single core
It should be noted that back then, I'd never seen a line of Java, let alone written any. At that stage, I was only running existing apps - mostly JEdit, but also a handful of other smaller tools, which exhibited the poor performance mentioned.
Strict boolean conditionals is a wonderful idea that I wish C/C++ and Python would adopt. Java’s problem is that it stuffed up its boolean type. See good old Pascal or Modula-2 for examples of how to implement booleans properly.
ReplyDelete