我一直在想Java泛型的某些怪异方面以及通配符的使用。假设我有以下API:

public interface X<E> {
    E get();
    E set(E e);
}

然后,假设我们声明以下方法:
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());
    }
}

从我的“直观”理解来看,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());
    }
}

另一个例子:
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());
    }
}

换句话说,编译器知道x.set()中的通配符与x.get()中的通配符在形式上是相同的。为什么它不能使用这些信息? JLS中是否有形式上的方面,可以解释这种缺乏编译器“功能”的方面?

最佳答案

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

给定声明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;
}

这显然不会编译,但是通过您建议的增强功能,它可以编译。但是,我们可以在不更改JLS的情况下获得四种方法中的三种:
  • 对于a,当我们接收通用对象作为参数时,可以使该方法通用并接收X<T>而不是X<?>
  • 对于bc,当使用本地声明的引用时,我们不需要使用通配符声明该引用(Microsoft C#编译器团队的Eric Lippert称这种情况为“如果这样做很麻烦,那么就不要这样做了。 !”)。
  • 对于d,当使用无法控制其声明的引用时,除了编写辅助方法外,我们别无选择。

  • 因此,更智能的类型系统实际上只会在无法控制变量声明的情况下提供帮助。在这种情况下,做您想做的事情有点粗略-通配通用类型的行为通常意味着该对象仅打算作为生产者(? extends Something)或消费者(? super Something)工作对象,而不是两者兼有。

    TL; DR版本的好处是,它给JLS增加了复杂性,但它带来的好处很小(并且要求通配符不能是当今的通配符!)。如果有人提出了一个非常有说服力的案例来添加它,那么该规范可以进行扩展-但是一旦存在,它就会永远存在,并且可能会导致无法预料的问题,因此请不要使用它,直到真正需要它为止。

    编辑:这些注释包含有关什么是通配符和不是通配符的更多讨论。

    09-15 11:48