6

此代码会导致 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(即使用原始类型)允许代码编译,就像将类型更改为foosimple一样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;
}
4

2 回答 2

4

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

您应该提交一个 Oracle 错误。我试图搜索相应的错误报告,但这张旧的已关闭票是我能找到的最接近这个问题的(IMO 很难搜索 Oracle 错误数据库)。有机会我会搜索更多,因为这个 javac 问题感觉很熟悉。

于 2013-07-17T22:53:27.610 回答
2

如果程序员想显式地将类型X转换为 type Y,语言可以允许它,假设程序员比编译器更了解。

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

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

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


在您的问题中,有两个地方可以插入通配符, inFoo< >和 in 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 有错误。


例如,让我们看看为什么 case00有意义并且应该编译

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

令人惊讶的是 case01不应该编译,尽管它感觉很明智。根本问题是,Iterable是变的,我们应该在几乎任何地方使用通配符。所以理想情况下我们应该声明

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

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

于 2013-07-18T00:46:57.327 回答