JLS如何指定不能在方法中正式使用通配符?

Luk*_*der 4 java generics wildcard jls

我一直想知道Java泛型的一些奇怪方面和通配符的使用.比方说,我有以下API:

public interface X<E> {
    E get();
    E set(E e);
}
Run Code Online (Sandbox Code Playgroud)

然后,假设我们声明了以下方法:

public class Foo {
    public void foo(X<?> x) {
        // This does not compile:
        x.set(x.get());
    }

    public <T> void bar(X<T> x) {
        // This compiles:
        x.set(x.get());
    }
}
Run Code Online (Sandbox Code Playgroud)

从我的"直觉"的理解,X<?>实际上是一样的X<T>,只是未知<T>正式未知的客户端代码.但在内部foo(),我猜测编译器可以推断(伪JLS代码)<T0> := <?>[0], <T1> := <?>[1], etc....这是大多数程序员明确而直观地做的事情.他们委托私人助手方法,导致许多无用的锅炉板代码:

public class Foo {
    public void foo(X<?> x) {
        foo0(x);
    }

    private <T> void foo0(X<T> x) {
        x.set(x.get());
    }
}
Run Code Online (Sandbox Code Playgroud)

另一个例子:

public class Foo {
    public void foo() {
        // Assuming I could instanciate X
        X<?> x = new X<Object>();
        // Here, I cannot simply add a generic type to foo():
        // I have no choice but to introduce a useless foo0() helper method
        x.set(x.get());
    }
}
Run Code Online (Sandbox Code Playgroud)

换句话说,编译器知道通配符in x.set()正式与通配符相同x.get().为什么不能使用这些信息?JLS中是否有正式的方面,这解释了这个缺乏编译器的"功能"?

gus*_*afc 5

你想知道它为什么不支持?一个同样有效的问题是,为什么要支持它?

通配符的工作方式,给定声明X<? extends Y> x,表达式的类型x.get()不是? extends Y或编译器推断出的某种类型,但是Y.现在,Y不能分配? extends Y,所以你不能传递x.get()x.set有效签名的void set(? extends Y e).你唯一可以传递给它的是null,它可以分配给每个引用类型.

我非常密切地关注JDK开发人员的邮件列表,而且一般的精神是JLS指定的每个功能都应该自己负担 - 成本/收益比应该在利益方面很重.现在,跟踪一个值的来源是很有可能的,但它只能解决一个特殊情况,它有几种其他方法可以解决它.考虑这些类:

class ClientOfX {
    X<?> member;
    void a(X<?> param) {
        param.set(param.get());
    }
    void b() {
        X<?> local = new XImpl<Object>();
        local.set(local.get());
    }
    void c() {
        member.set(member.get());
    }
    void d() {
        ClassWeCantChange.x.set(ClassWeCantChange.x.get());
    }
}

class ClassWeCantChange {
    public static X<?> x;
}
Run Code Online (Sandbox Code Playgroud)

这显然不能编译,但是根据您提出的增强功能,它确实可以.但是,我们可以在不更改JLS的情况下编译四种方法中的三种:

  1. 因为a,当我们收到通用对象作为参数时,我们可以使方法通用并接收X<T>而不是X<?>.
  2. 对于bc,当使用本地声明的引用时,我们不需要使用通配符声明引用(Microsoft的C#编译器团队的Eric Lippert称这种情况为"如果你这样做会伤害,那么就不要这样做! " ).
  3. 因为d,当使用我们无法控制其声明的引用时,除了编写辅助方法之外别无选择.

因此,更智能的类型系统实际上只有在我们无法控制变量声明的情况下才有用.在这种情况下,做你想要做的事情的情况有点粗略 - 通配类型的通配符通常意味着该对象要么仅用作对象的producer(? extends Something)或consumer(? super Something). , 不是都.

TL; DR版本是它带来的好处(并且要求通配符不是它们现在的东西!),同时增加了JLS的复杂性.如果有人想出一个非常引人注目的案例来添加它,那么规范可以延长 - 但是一旦它存在,就会永远存在,并且它可能会导致无法预料的问题,所以在你真正需要它之前就把它留下来.

编辑:评论包含更多关于通配符是什么和不通配符的讨论.