为什么不允许转换为“GenericType <! - ? - >”?

时间:2013-07-17 22:12:18

标签: java eclipse generics

此代码导致javac发生编译错误(但值得注意的是,不是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;
    }
}

javac的错误是:

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>

将强制转换更改为(Bar) foo(即使用原始类型)可以编译代码,将foo的类型更改为Foo<? extends Iterable<?>>也是如此。

编辑:非常有趣,这个简单的改变导致Eclipse拒绝,但javac接受:

void test(Foo<Iterable<String>> foo) {
    Bar<?> bar = (Bar<?>) foo;
}

而且,Eclipse和javac都拒绝这一个:

void test(Foo<Iterable<? extends String>> foo) {
    Bar<?> bar = (Bar<?>) foo;
}

2 个答案:

答案 0 :(得分:4)

您的代码是合理的并且应该编译:您将静态类型Foo缩小到Bar,同时将泛型类型? extends Iterable<? extends String>扩展为?,其中级别?是通配符捕获。这是一个经过检查的演员,它应该在没有警告的情况下进行编译。

你应该submit an Oracle bug。我试图搜索相应的错误报告,但this old closed ticket是我能找到的最接近这个问题的(IMO,Oracle错误数据库很难搜索)。当我有机会时我会搜索更多,因为这个javac问题非常熟悉。

答案 1 :(得分:2)

如果程序员想要明确地将类型X强制转换为类型Y,那么语言可以允许它,假设程序员比编译器更了解。

但Java的好人想要阻止一些明显不可能的演员表,例如(Cat)dog。所以他们有这个光荣的详细部分专门讨论这个主题 - http://docs.oracle.com/javase/specs/jls/se7/html/jls-5.html#jls-5.5.1

但是,这些规则有什么问题吗?并且,编译器是否符合这些规则? ......这些问题太复杂而且不太有趣。

我们应该关心的是演员是否有意义;如果它确实有意义,并且编译器拒绝接受它,没问题,只需解决它。


在您的问题中,有两个地方可以在Foo< >Iterable< >中插入通配符。在每个地方,通配符都可以是

    0. None
    1. Bounded    "? extends Something"
    2. Unbounded  "?"

所以让我们探索所有组合,这是我的结论:

     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意味着演员是否有意义,稍后会解释。

如果10 and 11,代码应该编译,但javac拒绝它。要么规则有问题,要么javac有错误。


让我们看看,为什么案例00有意义,应该编译

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.

以及为什么案例01不应该编译

     Foo<Iterable<X>> <: Foo<Iterable<? extends String>>
=>   Iterable<X> = Iterable<? extends String>
=>   NO SOLUTION
     note that Iterable<String> != Iterable<? extends String>

和案例11

     Foo<Iterable<X>>  <: Foo<? extends Iterable<? extends String>>
=>   Iterable<X> <: Iterable<? extends String>
=>   X <: String

令人惊讶的是案例01不应该编译,尽管它感觉合情合理。根本问题是,Iterable是可变的,我们应该在它使用的任何地方使用通配符。理想情况下,我们应该声明

class Bar<T> implements Foo<Iterable<? extends T>>

但如果我们到处插入通配符,生活就是地狱。更好的解决方案是声明站点差异。不确定Java是否会在我们退休之前添加该功能。