Java puzzler: Generic type inference
Here’s an issue I ran into while hacking a type system to work with some very inflexible generated code; I’ve distilled the strangeness into the simplest form I could.
Suppose you have a simple type hierarchy:
public class A { }
public class B extends A { }
public class C extends A { }
Then, it turns out that the following code works just fine:
List<Class<? extends A>> list = ImmutableList.of(B.class, C.class);
Surprisingly, however, the following code fails to compile(!):
List<Class<? extends A>> list = ImmutableList.of(B.class);
with the error
incompatible types
found : com.google.common.collect.ImmutableList<java.lang.class<B>>
required: java.util.List<java.lang.class<? extends A>>
List<Class<? extends A>> list = ImmutableList.of(
...
That’s right; adding the parameter makes this typesafe! (Note that my example uses ImmutableLists from the Google Common Collections, but this is actually happening because of Java’s type inference mechanism, not anything related to that particular class). Of course, it’s easy to work around it, but the interesting thing is what can be learned about Java by understanding the problem here.
I’ll post a “solution” in a few days, in case anyone wants to give it a shot.



One must remember to realize that if A is a subclass of B, SomeClass<A> is NOT a subclass of SomeClass<B>. eg (which would cause an error):
ArrayList<Object> foo = new ArrayList<Integer>();The reason why the first case worked was because when the compiler finds 2 different classes (extendable to arbitrary number of classes), it tries to find the common superclass and interface and then it becomes an object of that type. There is a simpler example of this first case, but I am too lazy to write it up.
I am interested in seeing the solution to this specific problem, since the class Class is special and casting B.class back up does not work.
William
October 14, 2010 at 4:57 am
Yes, that’s pretty much correct — the nearest common ancestor when it’s inferring based on B and C is A, but either B or C by itself can only infer to themselves, which is not compatible with
List<Class<? extends A>>since generic types are not contravariant.The practical solution is to use Builder. The most straightforward solution however, as suggested by Minsang, is to parametrize of():
ImmutableList.<Class<? extends A>>of(B.class);(Also, I fixed up the formatting of your reply. Not your fault, the escaping here just sucks.)
Adrian Petrescu
October 14, 2010 at 5:00 am
Wow, never knew you can parametrize functions like that. Sweet.
William
October 29, 2010 at 4:13 am