An unexpected failure of the Java compiler. Or is it a failure of the Java type system?
Java programmers write repetitive code like
HashMap<String, Integer> foo = new HashMap<String, Integer>();
all the time. Those who seek beauty in their code may choose to use the Google collections library and write instead
HashMap<String, Integer> foo = Maps.newHashMap();
Constructors and static methods interact in very different ways
with generics. If you say new Foo()
you instantiate a raw type, which is quite different from a what
you get by saying new Foo<Bar>()
.
On the other hand, static methods are the beneficiaries of a little
bit of type inference, as specified by sections
15.12.2.7 and
15.12.2.8
of the Java Language Specification (Third Edition). The actual
rules are complicated (and I haven't read them yet) but the idea
seems to be to use two sources of information:
- the types of the actual arguments
- if the result is "assignment convertible" to some type T then this type T is used too.
The reason why the Google collections library works is the
second bullet: The compiler uses the type of foo
to figure out how to instantiate the type arguments of
Maps.newHashMap
.
OK, that was the background. Now let's see some examples.
Would you expect the following code to compile?
interface A { static enum Foo { BAR; } static enum Bar { FOO; } } class B<T extends Enum<T>> { private B() {} public static <T extends Enum<T>> B<T> get() { return new B<T>(); } } public class C { public static void main(String[] args) { B<A.Foo> b = B.get(); } }
Yes, it works. The Sun Java compiler 1.6.0_13 has no problem in handling the sort-of recursive bound put on the type.
Now, would you expect the following to work?
interface A { static enum Foo { BAR; } static enum Bar { FOO; } } class B<T, S> { private B() {} public static <T, S> B<T, S> get() { return new B<T, S>(); } } public class C { public static void main(String[] args) { B<A.Foo, A.Bar> b = B.get(); } }
Again, it works flawlessly. After all, why would multiple type arguments confuse the compiler?
So, you'd expect the following to work too, won't you?
interface A { static enum Foo { BAR; } static enum Bar { FOO; } } class B<T extends Enum<T>, S extends Enum<S>> { private B() {} public static <T extends Enum<T>, S extends Enum<S>> B<T, S> get() { return new B<T, S>(); } } public class C { public static void main(String[] args) { B<A.Foo, A.Bar> b = B.get(); } }
Tough luck, though: It doesn't work. Apparently
"no instance(s) of type variable(s) T,S exist so
that B<T,S> conforms to B<A.Foo,A.Bar>".
I would have thought that T=A.Foo
and S=A.Bar
would do the job.
As I said, I haven't read yet the relevant sections in the Java specification to see if this a problem with the language or a problem with the compiler. The thing is that the two sections take 10 and a half screens on my monitor, which signals that I should put aside maybe one whole day for it, and I don't have one whole day to spend. My hope is that it is a bug in the compiler, because otherwise I'd loose some confidence in the language itself. After all, there's something fishy about a 10-pages algorithm that doesn't end up trying the obvious solution.
5 comments:
This code works for 1.6.0_14 on my Vista machine.
Thank you for trying. I get the error with 1.6.0_14 on Ubuntu.
I would expect, you use
public static <Q extends Enum<Q>> B<Q> get()
instead of
public static <T extends Enum<T>> B<T> get()
FWIW, this still doesn't compile on my computer, this time with 1.7.0_13 on Ubuntu.
It does compile now, with 1.7.0_55.
Post a Comment
Note: (1) You need to have third-party cookies enabled in order to comment on Blogger. (2) Better to copy your comment before hitting publish/preview. Blogger sometimes eats comments on the first try, but the second works. Crazy Blogger.