A Critique of Java by a .NET Developer
update: This post is a little out of date now with some of the coming features of Java 7 just around the corner, but it is mostly still accurate.
I have started to study the Java programming language again after spending a few years in C# land, and am finding many reminders of why I switched in the first place. Don’t get me wrong; I think Java is a great and necessary thing with a much stronger community than C#. Most importantly, Java works on almost every operating system under the sun, whereas .NET is only available on Microsoft platforms or as the eternally out-of-sync Mono project, which does not provide a the complete framework for the versions of the .NET framework that it supports.
I also have to respect the age of the languages in question, and that they were not designed to solve the same problems, at least not at first.
I’m a firm believer that one of the best ways to internalise new information is to write it down so here I’m sharing my critique of some of the differences between Java and .NET, focussing mainly on what I feel holds Java back from being great.
1. There is no safe way to trap integer overflows.
Java integer types do not overflow in the traditional sense when the value is out of range. Instead, they wrap around, so Integer.MAX_VALUE + 1 is a very large negative number, as shown below:
// Java
public class Main {
public static void main(String[] args) {
int a = Integer.MAX_VALUE;
System.out.println("overflow: " +
(a + 1)); // prints "-2147483648"
}
}
The erroneous value can of course be captured using some checking code, which is always going to be ugly. A little more flexibility would be nice.
C# behaves similarly by default, but includes the checked keyword to cause an exception to be thrown when a number overflows, enabling much more consistent and elegant error handling. Needless to say, this also makes debugging a lot easier.
2. No operator overloading
One of the great strengths of C++, Python, C# and many other languages is that operators can be defined and customised to each type. The .NET type system uses this to implement a unified type system where even basic integers and characters refer to an immutable struct and have methods of their own, avoiding over-reliance on static methods.
Java provides no such mechanism, so some numerical types behave very differently to others. String comparison is another task that benefits from operator overloading. Compare the following:
// Java
BigInteger result = a.add(b);
if (myString.equals("hello"))
return true;
// C#
BigInteger result = a + b;
if (myString == "hello")
return true;
3. No instance properties
Getters and setters are everywhere in Java. A method is a perfectly acceptable and simple way of accessing or mutating an instance variable. The problem lies in how ugly it is to use methods for everything.
C# properties allow us to use simple assignment and arithmetic operators to access and change instance variables, exactly like using public members, but with the added safety of a code wrapper, effectively hiding both the field itself, and the ugly code that can result from incrementing a variable with a getter and setter. Compare the following:
// Java
setCounter(5);
setCounter(this.getCounter() + 1);
setCounter(this.getCounter() + 2);
// C#
Counter = 5;
Counter++;
Counter += 2;
4. Limited utility of the switch statement
The Java switch statement (up to and including Java 6) does not accept strings. Most of the time, this isn’t a problem and any strings that need to be ‘switched on’ are usually better parsed into a more sensible format first, like an enumerated type. However, there will always be odd edge cases where it doesn’t make sense to convert the string, or doing so would be horribly messy. In these cases, a simple case “string” would be useful.
This limitation is being addressed for Java 7. About time, too.
5. The language improves very slowly
Both a strength and a weakness, the development of the Java language has been very slow in recent years. This is great for compatibility and makes it easy to learn, as it is less of a moving target that some other languages. It also makes the language less interesting, as it is slow to fix bad design decisions like the original Date and Calendar classes. The syntax of the language is taking a few great evolutionary steps in Java 7, with the ability to switch on strings and automatic resource clean-up that simplifies a lot of code.
C# moves a lot more quickly, being controlled by just one company even if they do take a lot of user feedback. It has had string switching and the IDisposable interface and accompanying syntax for the same purpose for a while. Personally, I like living on the cutting edge and I use Windows most of the time, but I still don’t appreciate the vendor lock-in.
// Java
// from
try {
// get resource
// do something
} finally {
// dispose of it
}
// to
try (/* get resource*/) {
// do something
}
// C#
using ( /* get resource */) {
// do something
}
I have mostly been talking about the syntax of the Java and C# languages here, with a few reference to their associated type systems here and there. I should note that it is possible to work with the Java Virtual Machine (JVM) and the Common Language Infrastructure (CLI) of the .NET framework with a number of different languages supporting various programming styles and paradigms. The flexibility of the platforms is perhaps their greatest strength. For my money, Java wins on compatibility, whereas .NET wins with a better language and a better thought-out type system. Others will have different priorities. I am quite excited to get to know Java 7 as it develops in any case.
Update: stuff I missed
It has been pointed out to me that I missed a couple of important areas of comparison.
GUI toolkits
I haven’t got to grips with user interfaces in Java yet, but I learned most of my tricks with WPF and XAML in .NET. Using anything else makes my head hurt, so I’m inclined to think I’ve been spoiled.
Extension methods
C# supports a wonderful feature called Extension Methods that lets you add new methods to just about any type without having access to that type’s source code. I did this with my parallel map function the other week.
Java does not support adding arbitrary methods to defined types without editing their source code directly. Inheritance can handle some use cases but doesn’t cover every eventuality.
The very general approach taken by Google Go is even more concise. In Go methods are defined with an optional receiver as part of their signature, providing a single, general syntax that covers all options.