Beware Java’s half baked generics
Usually I don’t badmouth Java. I think its a very good programming language.
In fact, I tend to defend it in arguments on various forums.
Sure, it lacks features compared to some other languages, but then again throwing everything including a kitchen sink in to a language is not necessarily a good idea. Just look at how easy it is to get a horrible mess of code in C++ with single operator doing different things depending on context. Is
&some_var trying to get address of a variable or a reference? And what does
&&some_var do? It has nothing to do with the boolean AND operator!
So here we have a simple language friendly to new developers, which is good because there are lots of those using it on the popular Android platform.
Unfortunately, even the best languages have some implementation detail that will make you want to lynch their creators or just reap out your hair, depending on whether you externalize your violent tendencies or not.
Here is a short code example that demonstrates a bug that for about 5 minutes made me think I was high on something:
HashMap<Integer, String> map = new HashMap<>(); byte a = 42; int b = a; map.put(b, "The answer!"); if (map.containsKey(a)) System.out.println("The answer is: " + map.get(a)); else System.out.println("What was the question?");
What do you expect this code to print?
Will it even compile?
Apparently it will, but the result will surprise anyone who is not well familiar with Java’s generic types.
Yes folks – the key will not be found and the message
What was the question? will be printed.
Here is why:
The generic types in Java are not fully parameterized. Unlike a proper C++ template, some methods of generic containers take parameters of type Object, instead of the type the container instantiation was defined with.
HashMap, even though it’s
add is properly parameterized and will raise a compiler error if the wrong type key is used, the
containsKey methods take a parameter of type
Object and will not even throw a runtime exception if the wrong type is provided. They will simply return
false respectively as if the key was simply not there.
The other part of the problem is that primitive types such as
int are second class citizens in Java. They are not objects like everything else and can not be used to parameterize generics.
They do have object equivalents named
Integer but those don’t have proper operator overloading so are not convenient for all use cases.
Thus in the code sample above the variable
a gets autoboxed to
Byte, which as far as Java is concerned a completely different type that has nothing to do with
Integer and therefore there is no way to search for
Byte keys in
A language that implements proper generics would have parameterized these methods so either a compilation error occurred or an implicit cast was made.
In Java, it is up to you as a programmer to keep you key type straight even between seemingly compatible types like various size integers.
In my case I was working with a binary protocol received from external device and the function filling up the map was not the same one reading from it, so it was not straight forward to align types everywhere. But in the end I did it and learned my lesson.
Maybe this long rant will help you too. At least until a version of Java gets this part right…