Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why isn't a conversion to "GenericType<?>" allowed here?

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;
}
like image 494
Tavian Barnes Avatar asked Jul 17 '13 22:07

Tavian Barnes


2 Answers

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.

like image 98
Paul Bellora Avatar answered Nov 10 '22 02:11

Paul Bellora


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.

like image 2
ZhongYu Avatar answered Nov 10 '22 01:11

ZhongYu