Every now and again, just as I think I’m getting sick of C++ and all it’s foibles, I learn something about Java that makes me consider how lucky I truly am. Today, I present two idiocies in Java that will make you blink a lot and go “What?”
First up, Generics. In C++, there is a hugely powerful mechanism for parameterising types – templating. You can define a generic class, and can then specialise it on particular types – for example, a generic list class, which can be specialised into a list of ints, or of floats, or of ThisArbitraryClassIHaveJustInvented. When in the hands of someone like Andrei Alexandrescu they become almost other-worldly in power and flexibility. So, generics are largely considered to be A Good Thing.
Sun decided Java should get generics. Fair enough. However, Sun also have a huge hardon for preserving backwards compatibility, which meant that they had to do it in such a way as to preserve bytecode compatibility between versions. Generics, in C++, require the compiler to generate code for every specialised instance of a class – in Java, where header files don’t exist, this obviously can’t be done. Also, in order to preserve backwards compatibility, they couldn’t change the bytecode format – they were stuck with what they had. So, what did they do? They cheated.
Java implements Generics by a process known as Type Erasure; stick with me here, because this is where things get crazy. Under type erasure, a generic class is compiled such that all instances of the parameterised type are replaced with Object (or an interface of some sort) in the compiled bytecode. When things use a specialisation of the generic class, the compiler does type checks on call boundaries to ensure the right types are being used – but otherwise, basically, the parameterised type disappears.
The net upshot of this is that you can’t actually use the parameterised type in methods of the generic class for anything other than casting (which generates a unchecked compiler warning). You can’t create new instances of that type; you can’t use is-a operations with it; basically, you can’t do a whole lot other than use it to do static (compile time) type checking on collections. Which is kind of useless, really.
Bruce Eckels has a good article about the whole thing.
Secondly, Integers. You wouldn’t have thought that there was a lot you could get wrong with Integers, but Sun have gone to some effort to make sure that, in combination with the equality operator, they are as confusing as possible. Consider the following code:
Integer i1 = 127;
Integer i2 = 127;
Integer j1 = 128;
Integer j2 = 128;
System.out.println(i1 == i2);
System.out.println(j1 == j2);
What would you expect that to print? Naively, coming from a C++ background, one would say “Well, it’d print true for each comparison – assuming operator== did what you expected it do.” Well, in Java, the == operator doesn’t quite do what you expect it to – although its revised definition isn’t totally unreasonable. It checks equality by reference, not value – so, references to two different objects will always return false on ==, but a two references to the same object will return true.
“Okay, that’s fair enough then”, says our C++ coder, “it’s like comparing pointers to objects – so that example above would actually print false twice, then?” Well, you’d think so, wouldn’t you. Unfortunately, this is Java, and things are never quite that simple.
The line Integer i1 = 127; actually gets compiled as Integer i1 = Integer.valueOf(127);. The valueOf() method returns an Integer object with the appropriate value; however, its behaviour isn’t as simple as that. For most values, it creates a new object and returns a reference to it, as you’d expect; however, for values between -128 and 127, it maintains an internal cache of statically created objects, and returns a reference to one of those instead. So, for any integers between -128 and 127, you’ll always get a particular, prebuilt object, but for any other integers, you’ll always get a new object.
So, in that example above, the counter-intuitive and utterly bizarre result is that it prints true for the first test and false for the second.
I’m so glad I’m not a Java programmer.
Edit: If you’re a Java programmer, you’re probably quite upset and annoyed with me now. Yes, I know you should use equals() and not == to test quality between objects; that’s not my point here. My point is that the specialised behaviour for 8-bit integers is inconsistent, utterly counter-intuitive and breaks the principle of least surprise quite horribly. And if you don’t agree with that, then I’m afraid Sun have finally got to you; report to your local cult de-programming centre and start your journey into a wider, freer world.
As I said before, you have to have written really terrible code before you’ll even hit the Integer thing.