Wednesday, January 11, 2012

Java Generics are a Lemon

After working with Java for almost 15 years and deep knowledge of Java generics on the class format level I learned something very basic the really, really hard way. I knew the collections in Java were not that good in comparison what you find in other environments (immutable anyone?) but now I learned that even adding all that extra cruft on my classes is useless when you have a major refactoring.

This week I learned that for the collections and maps the get, remove, containsKey, containsValue, and equals methods do not use the generic type parameter. This means you can call it with any type and you do not get an error if you call it with a type that is not compatible with the generic type of the collection.

I found this out when I changed many Map types to take another key type, expecting that Eclipse would nicely point me out what to change. Well it does not. The puts and parameter calls are nicely pointed out but a significant amount of code fails because it always fails because the object is now no longer found. Fortunately I am saved by having hundreds of solid test cases that tell me where to look.

I understand these methods were not generified because things became too hairy. Why that did not raise concerns about the power of the generics at the time beats me.

Well, guess I learned something.

Peter Kriens

9 comments:

  1. Java is old; there are limits to changes that can be done. Use Groovy or something similar.

    ReplyDelete
  2. It can be puzzling and I've wondered why myself. There's more to it than it "being hairy".

    The answer here and subsequent comments are enlightening:

    http://stackoverflow.com/questions/104799/why-arent-java-collections-remove-methods-generic

    ReplyDelete
  3. I expect that it seemed safe to punt on those methods because — if you're willing to add run-time type checking to each of them — they can rather quickly answer "no." That is, if this Map is expected to hold keys of type String, and you ask if it has an entry for a key of type Integer, then the answer is "obviously" no.

    It's a weak argument, though, because it could apply to just about every method argument. It so happens here that these methods have conventional ways of saying "no," without needing to throw an exception to indicate that "yes" could never have been possible anyway.

    ReplyDelete
  4. Think about it a bit harder. Formally speaking, equality (which is what those methods are all about) is nothing to do with class. Instances of two completely unrelated classes could be still be equal and therefore still act as keys for the map. How can you possibly hope to make that generic and backwards compatible?

    ReplyDelete
  5. I find it very helpful to not use Java collections all over the place but to wrap them into my own classes, each of which is perfectly suited for its specific tasks. This way, there are just very few places that you need to change...

    ReplyDelete
  6. @Vijay: Well, with groovy you loose static typing and I it has grown on me for complex and large apps. With dynamic typing there are lots of options.

    @Chris: That answer just shows that generics are not well thought out where the key problem is wildcards. Trying out some examples it looks like the <? extends X&gt syntax makes the collection more or less immutable. However, this is only necessary for methods that add to the collection. I.e. for Collection&ly;T extends Number&gt add(Long) is an error but remove(Long) should be np. However, there is no way to specify this constraint. It just points out there is something fundamentally broken imho.

    @seh: the whole idea of generics and type safety is that you can make massive refactorings and get errors. This is basically not possible with the current design.

    @Chris: You're case is not very common and and can be implemented with Collection>Object&lt.

    ReplyDelete
  7. @Vijay: Well, with groovy you loose static typing and I it has grown on me for complex and large apps. With dynamic typing there are lots of options.

    @Chris: That answer just shows that generics are not well thought out where the key problem is wildcards. Trying out some examples it looks like the <? extends X> syntax makes the collection more or less immutable. However, this is only necessary for methods that add to the collection. I.e. for Collection<T extends Number> add(Long) is an error but remove(Long) should be np. However, there is no way to specify this constraint. It just points out there is something fundamentally broken imho.

    @seh: the whole idea of generics and type safety is that you can make massive refactorings and get errors. This is basically not possible with the current design.

    @Chris: You're case is not very common and and can be implemented with Collection<Object>.

    ReplyDelete
  8. @Peter: It's not that wildcards weren't well thought out - if you want variant generics, you have to give up covariant method parameters. We can't do anything about the type theory, but we did improve the error messages around capture conversion in JDK7, to better explain why an actual parameter type doesn't satisfy the constraints on a (captured) formal parameter type.

    ReplyDelete
  9. @Alex: I know how tricky this area is but not having the generic types on remove/get/contains in collections is incredibly painful when you change types in an application, the very idea that was supposed to be easy in a static typed language.

    ReplyDelete