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.