Blog About Contact

Unfortunately Interesting Java Generics

Published Thu, 16 Jul 2009

Man on MouseThe last couple of days I have been trying to come up with a sneaky way of creating an abstraction layer between a business object model and an external object model. In general these two models would be the same and in sync, but occasionally will need to be out of sync, in order to satisfy version control and backwards compatibility in the external interface.

I had planned to use separate interfaces bound to a single implementation class with Guice - thereby keeping a single implementation so long as the models are in sync, and having an interface contract that will be break should a developer try to modify the model without understanding the implications to the dependent layers.

This seemed like a great approach, but my less-than-deep understanding of Java Generics has thrown a spanner into the works.

This article at IBM Developerworks is a good read -

[Generics] bear a superficial resemblance to templates in C++, both in their syntax and in their expected use cases. But the similarity is only skin-deep -- generics in the Java language are implemented almost entirely in the compiler, which performs type checking and type inference, and then generates ordinary, non-generic bytecodes.
This key point here is that generics are implemented almost entirely in the Java compiler. This was something I was aware of, but hadn't really given any thought as to what that means. At the simplest level, that makes a class such as this one, invalid -
  public class DoesNotCompile {
      public void setList(List<String> stringList);
      public void setList(List<Integer> integerList);
  }
Whereas the non-generic equivalent is perfectly valid -
  public class CompilesJustFine {
      public void set(String string);
      public void set(Integer integer);
  }
This is because that while List<String> and List<Integer> are differentiated at compile time, they are both just List classes that are indistinguishable from one-another.
Because of erasure, List<Integer> and List<String> are the same class, and the compiler only generates one class when compiling List<V> (unlike in C++). As a result, the compiler doesn't know what type is represented by V when compiling the List<V> class, and so you can't do certain things with a type parameter (the V in List<V>) within the class definition of List<V> that you could do if you knew what class was being represented.
Also, parent-child class relationships don't work as you might expect on first perception. For example you might think it is reasonable to use a List<Integer> where a List<Number> is required. Integer is a child class of Number, why isn't that valid?
It turns out there's a good reason it doesn't work that way: It would break the type safety generics were supposed to provide. Imagine you could assign a List<Integer> to a List<Number>. Then the following code would allow you to put something that wasn't an Integer into a List<Integer>:
List<Integer> li = new ArrayList<Integer>();
List<Number> ln = li; // illegal
ln.add(new Float(3.1415));

But. we can work around that by using List<? extends Number> instead, thereby making it clear we're not fussed if it's a Number, or any of it's children.

learning to use generics will almost certainly provide some opportunity for head-scratching (and sometimes cursing) along the way.

Yep. And as for my actual problem with the interface binding? Well my question at stackoverflow.com tells the story :)


About the Author

Richard Nichols is an Australian software engineer with a passion for making things.

Follow him on twitter or subscribe by RSS or email.

You might also enjoy reading -


Discuss / Comment

No one has commented yet.

Add a comment

  • {{e.error}}

Thanks for your comment!/

Required.
Valid email address required.
Required.
Posting message, please wait...