5

抱歉标题含糊。我有这段代码可以在 Eclipse Juno (4.2) 上编译,但不能在 javac (1.7.0_09) 上编译:

package test;

public final class Test {
    public static class N<T extends N<T>> {}

    public static class R<T extends N<T>> {
        public T o;
    }

    public <T extends N<T>> void p(final T n) {}

    public void v(final R<?> r) {
        p(r.o);       // <-- javac fails on this line
    }
}

错误是:

Test.java:13:错误:Test 类中的方法 p 不能应用于给定类型;
        p(ro);
        ^
  要求:T
  找到:N<CAP#1>
  原因:推断类型不符合声明的边界
    推断:N<CAP#1>
    界限:N<N<CAP#1>>
  其中 T 是一个类型变量:
    T 扩展了在方法 <T>p(T) 中声明的 N<T>
  其中 CAP#1 是一个新的类型变量:
    CAP#1 扩展了 N<CAP#1> 从捕获的 ?
1 个错误

所以问题是:

  1. 这是一个javac错误还是 Eclipse 错误?

  2. 有什么方法可以在javac不更改v方法签名的情况下进行编译(即保留通配符)?

    我知道将其更改为<T extends N<T>> void v(final R<T> r)确实可以编译,但我想知道是否有办法首先避免这种情况。此外,该方法p无法更改为,<T extends N<?>> void p(final T n)因为内容具有需要精确约束的类型T extends N<T>

4

1 回答 1

6

通配符的局限性在于它们会破坏T extends X<T>类型参数所允许的递归表达式。基于以下几点,我们知道您尝试做的事情是安全的:

  1. r.o是类型T(由 声明R),即是或扩展N<T>
  2. 该方法p接受一个类型的参数T(由 声明p),它也是或 extends N<T>
  3. 因此,即使r键入为R<?>,理论上调用p(r.o)也应该是合法的。

这可能是 eclipse 编译器的原因(已知可以正确考虑javac 没有的泛型的某些细微差别)。

假设你想用 javac 编译并且不能v像你提到的那样改变签名,你能做的最好的就是使用原始类型,它“选择退出”泛型类型检查:

public void v(final R<?> r) {
    //necessary to placate javac - this is okay because [insert above reasoning]
    @SuppressWarnings("rawtypes")
    N nRaw = r.o;
    p(nRaw);
}
于 2012-11-03T23:42:45.923 回答