Java CRTP和通配符:代码在Eclipse中编译,但不是`javac`
对不起,标题含糊不清。 我有这段代码编译Eclipse Juno(4.2)但不是javac(1.7.0_09):
package test; public final class Test { public static class N<T extends N> {} public static class R<T extends N> { public T o; } public <T extends N> void p(final T n) {} public void v(final R r) { p(ro); // <-- javac fails on this line } }
错误是:
Test.java:13:错误:类Test中的方法p不能应用于给定的类型; P(ω); ^ 要求:T 发现:N reason:推断类型不符合声明的边界 推断:N bound(s):N <N > 其中T是一个类型变量: T扩展方法 p(T)中声明的N 其中CAP#1是一个新的类型变量: CAP#1扩展N 从捕获? 1错误
所以问题是:
-
这是一个
javac
bug还是Eclipse bug? -
有没有办法在
javac
上进行javac
,而不更改v
方法的签名(即保留通配符)?我知道将它更改为
<T extends N> void v(final R r)
确实使它编译,但我想知道是否有办法首先避免这种情况。 此外,方法p
不能改变为<T extends N> void p(final T n)
因为内容具有需要精确约束T extends N
。
通配符的局限性在于它们会破坏类型参数允许的T extends X
类的递归表达式。 根据以下内容,我们知道您要做的是安全的:
-
ro
是T
型(由R
声明),它是或者延伸N
。 - 方法
p
采用类型为T
的参数(由p
声明),它也是或者扩展N
。 - 因此即使
r
被输入为R>
,呼叫p(ro)
理论上应该是合法的。
这可能是eclipse编译器的推理(已知对javac没有的generics的某些细微差别做出正确的限制)。
假设您想使用javac编译并且不能像您提到的那样更改v
的签名,那么您可以做的最好就是使用原始类型,它“选择”generics类型检查:
public void v(final R> r) { //necessary to placate javac - this is okay because [insert above reasoning] @SuppressWarnings("rawtypes") N nRaw = ro; p(nRaw); }