This code causes a compile error with javac (but, notably, not with Eclipse 4.2.2!):
public interface Foo<T> {
}
class Bar<T> implements Foo<Iterable<T>> {
}
class Test {
void test(Foo<? extends Iterable<? extends String>> foo) {
Bar<?> bar = (Bar<?>) foo;
}
}
The error from javac is this:
Foo.java:9: error: inconvertible types
Bar<?> bar = (Bar<?>) foo;
^
required: Bar<?>
found: Foo<CAP#1>
where CAP#1 is a fresh type-variable:
CAP#1 extends Iterable<? extends String> from capture of ? extends Iterable<? extends String>
Changing the cast to (Bar) foo
(i.e. using the raw type) allows the code to compile, as does changing the type of foo
to simply Foo<? extends Iterable<?>>
.
EDIT: Hilariously, this simple change causes Eclipse to reject, but javac to accept:
void test(Foo<Iterable<String>> foo) {
Bar<?> bar = (Bar<?>) foo;
}
And, both Eclipse and javac reject this one:
void test(Foo<Iterable<? extends String>> foo) {
Bar<?> bar = (Bar<?>) foo;
}
Your code is reasonable and should compile: you're narrowing the static type Foo
to Bar
while widening the generic type ? extends Iterable<? extends String>
to ?
, where the top-level ?
are wildcard-captures. It's a checked cast and it should compile without warning.
You should submit an Oracle bug. I tried to search for a corresponding bug report but this old closed ticket was the closest I could find to this issue (IMO the Oracle bug database is very hard to search). I'll search more when I get a chance because this javac issue feels very familiar.
If a programmer wants to explicitly cast a type X
to type Y
, the language could just allow it, assuming that the programmer knows better than the compiler.
But the good guys at Java want to prevent some obviously impossible casts, e.g. (Cat)dog
. So they have this glorious detailed section dedicated to the subject - http://docs.oracle.com/javase/specs/jls/se7/html/jls-5.html#jls-5.5.1
But, are there any problems with these rules? And, do compilers conform to these rules? ... These questions are too complex and not very interesting.
What we should care is whether the cast makes sense; if it does make sense, and the compiler refuses to accept it, no problem, just work around it.
In your question, there are two places to insert wildcards, in Foo< >
, and in Iterable< >
. In each place, the wildcard can be
0. None
1. Bounded "? extends Something"
2. Unbounded "?"
so let's explore all combinations, and here's my conclusion:
wildcard#1 wildcard#2 should_compile javac eclipse
00 - - Y Y N
01 - ? extends N N N
02 - ? N N N
10 ? extends - Y N Y
11 ? extends ? extends Y N Y
12 ? extends ? Y Y Y
20 ? - Y Y Y
should_compile
means the cast makes sense or not, explained later.
In case 10 and 11
, code should compile, but javac rejects it. Either the rules have problems, or javac has bugs.
Let's see for example, why case 00
makes sense and should compile
void test00(Foo<Iterable<String>> foo) {
Bar<?> bar = (Bar<?>) foo;
}
the question is, could there be a class/interface `X`, such that
Bar<X> <: Foo<Iterable<String>>
=> Foo<Iterable<X>> <: Foo<Iterable<String>>
=> Iterable<X> = Iterable<String>
=> X = String
so the answer is yes, the cast makes sense.
and why case 01
should not compile
Foo<Iterable<X>> <: Foo<Iterable<? extends String>>
=> Iterable<X> = Iterable<? extends String>
=> NO SOLUTION
note that Iterable<String> != Iterable<? extends String>
and case 11
Foo<Iterable<X>> <: Foo<? extends Iterable<? extends String>>
=> Iterable<X> <: Iterable<? extends String>
=> X <: String
It is surprising that case 01
should not compile, although it feels sensible. The root problem is, Iterable
is convariant, and we ought to use a wildcard with it almost anywhere it's used. So ideally we should declare
class Bar<T> implements Foo<Iterable<? extends T>>
but life is hell if we insert wildcards everywhere. The better solution is declaration site variance. Not sure if Java will ever add that feature before we all retire.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With