Ever since Generics were included in Java, they’ve been a great help and source of despair at once. One thing that most newcomers will stumble upon sooner or later is “Type Erasure” and its effects. You may read about it in the Java Tutorial and never quite understand it, until you encounter it in the wild (in your code) and it just laughs at your carefully crafted type system construct. This is the time when you venture into the deep end of the Java language specification and aren’t seen for days or weeks. And when you finally reappear, you are a broken man – or a strong warrior, even stronger than before, charged with the wisdom of the ancients.
The problem
If my introduction was too mystic for your taste – bear with me. The rest of this blog post is rather technical and bleak. It won’t go into the nitty-gritty details of Java generics or type erasure, but describe a real-world problem and one possible solution. The problem can be described by a few lines of code:
List<Integer> integers = new ArrayList<Integer>(); Iterable<Integer> iterable = integers; Iterable<Number> numbers = integers; // Damn!
The last line won’t compile. Let’s examine it step by step:
- We create a list of Integers
- The list can be (up-)casted into an Iterable of Integers. Lists are/behave like Iterables.
- But the list cannot be casted into an Iterable of Number, even though Integers are/behave like Numbers.
The compiler error message isn’t particularly helpful here:
Type mismatch: cannot convert from List<Integer> to Iterable<Number>
This is when we remember one thing about Java Generics: They aren’t exactly variant. While they have “use-site variance”, we are in need of “declaration-site variance” here, which Java Generics lack entirely. Don’t despair, this was all the theoretical discussion about the topic for today. If you want to know more, just ask in the comment section. Perhaps we can provide another blog post discussing just the theory.
The workaround
In short, our problem is that Java is unable to see the relationship between the types Integer and Number when given as generic parameter. But we can make it see:
List<Integer> integers = new ArrayList<Integer>(); List<Number> numberList = new ArrayList<Number>(); numberList.addAll(integers); Iterable<Number> numbers = numberList;
This will compile and work. I’ve split the creation and filling of the second List into two steps to make more clear what’s happening: By explicitely creating a new collection and (up-)casting every element of the List alone, we can show the compiler that everything’s ok.
The Klingonian Cast
Well, if the compiler wants to see every element of our initial collection to be sure about upcasting, we should show him. But why create a new List and swap the elements by hand every time, when we can just use the “Klingonian Cast“? Ok, I’ve made the name up. But how else would you call a structure that’s essentially an upcast, but using two generic parameters and a dozen lines of code if not something very manly and bold. But enough mystery again, let’s look at the code:
List<Integer> integers = new ArrayList<Integer>(); Iterable<Number> numbers = MakeIterable.<Number>outOf(integers);
The good thing about the Klingonian cast is that it has a very thin footprint at runtime. Your hotspot compiler might even eliminate it completely. But you probably don’t want to hear about it characteristics, but see the implementation:
public class MakeIterable { public static <T> Iterable<T> outOf(final Iterable<? extends T> iterable) { return new Iterable<T>() { @Override public Iterator<T> iterator() { return iteratorOutOf(iterable.iterator()); } }; } protected static <T> Iterator<T> iteratorOutOf(final Iterator<? extends T> iterator) { return new Iterator<T>() { @Override public boolean hasNext() { return iterator.hasNext(); } @Override public T next() { return iterator.next(); } @Override public void remove() { iterator.remove(); } }; } }
That’s it. A “simple” upcast for Java Generics, ready to use it for your own convenience. Enjoy!