此代码导致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;
}
答案 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是否会在我们退休之前添加该功能。