为什么在有界通配符泛型中不能有多个接口?

我知道 Java 的泛型类型有各种各样的违反直觉的属性。有一件事我不明白,希望有人能给我解释一下。当为类或接口指定类型参数时,可以绑定它,以便它必须使用 public class Foo<T extends InterfaceA & InterfaceB>实现多个接口。然而,如果你实例化一个实际的对象,这不再工作了。List<? extends InterfaceA>没问题,但是 List<? extends InterfaceA & InterfaceB>编译失败。考虑以下完整的片段:

import java.util.List;


public class Test {


static interface A {
public int getSomething();
}


static interface B {
public int getSomethingElse();
}


static class AandB implements A, B {
public int getSomething() { return 1; }
public int getSomethingElse() { return 2; }
}


// Notice the multiple bounds here. This works.
static class AandBList<T extends A & B> {
List<T> list;


public List<T> getList() { return list; }
}


public static void main(String [] args) {
AandBList<AandB> foo = new AandBList<AandB>(); // This works fine!
foo.getList().add(new AandB());
List<? extends A> bar = new LinkedList<AandB>(); // This is fine too
// This last one fails to compile!
List<? extends A & B> foobar = new LinkedList<AandB>();
}
}

似乎 bar的语义应该是定义良好的——我不认为允许两种类型的交叉而不仅仅是一种类型会造成类型安全性的损失。我相信一定有原因的。有人知道这是什么吗?

18371 次浏览

问得好,我花了很长时间才想明白。

让我们简化一下你的情况: 你试图做同样的事情,就像你声明一个类扩展了2个接口,然后一个变量的类型是2个接口,类似这样:

  class MyClass implements Int1, Int2 { }


Int1 & Int2 variable = new MyClass()

当然,是非法的。 这相当于你尝试用泛型做的事情。 你要做的是:

  List<? extends A & B> foobar;

但是,这样使用 foobar,您需要同时使用两个接口的变量:

  A & B element = foobar.get(0);

也就是 Java 中的 不合法。这意味着,您将同时声明列表的元素为两种类型,以及 即使我们的大脑能够处理它,Java 语言也不能。

来自 Java 语言规范:

4.9交叉口类型 交类型采用 T1 & ... & Tn,n > 0的形式,其中 Ti,1in 是类型表达式。交叉类型出现在捕获转换(5.1.10)和类型推断(15.12.2.7)的过程中。不可能将交集类型作为程序的一部分直接编写; 没有语法支持这一点.交集类型的值是那些是所有类型 Ti 的值的对象,表示1英寸。

那么,为什么不支持这种做法呢?我的猜测是,你应该怎么处理这样的东西?让我们假设这是可能的:

List<? extends A & B> list = ...

那该怎么办

list.get(0);

返回?没有捕获 A & B返回值的语法。在这样的列表中添加一些东西也是不可能的,所以它基本上是无用的。

没问题... 只需在方法签名中声明所需的类型。

结果显示:

public static <T extends A & B> void main(String[] args) throws Exception
{
AandBList<AandB> foo = new AandBList<AandB>(); // This works fine!
foo.getList().add(new AandB());
List<? extends A> bar = new LinkedList<AandB>(); // This is fine too
List<T> foobar = new LinkedList<T>(); // This compiles!
}

有趣的是,接口 java.lang.reflect.WildcardType看起来既支持通配符参数的上界,也支持下界; 而且每个上界都可以包含多个边界

Type[] getUpperBounds();
Type[] getLowerBounds();

这远远超出了语言所允许的范围。源代码中有一个隐藏的注释

// one or many? Up to language spec; currently only one, but this API
// allows for generalization.

接口的作者似乎认为这是一个偶然的限制。

对于您的问题,一般的答案是,泛型已经太复杂了; 增加更多的复杂性可能会被证明是最后一根稻草。

为了允许通配符有多个上界,必须扫描规范并确保整个系统仍然可以工作。

我所知道的一个问题是类型推断。当前的推理规则根本不能处理交集类型。没有减少约束 A&B << C的规则。如果我们减少到

    A<<C
or
A<<B

任何当前的推理机都必须经过重大改革,才能允许这种分歧。但是真正严重的问题是,这允许多种解决方案,但是没有理由选择一种而不是另一种。

但是,推断对于类型安全并不是必不可少的; 在这种情况下,我们可以简单地拒绝推断,并要求程序员显式地填写类型参数。因此,推断的困难并不是反对截取类型的有力论据。

值得一提的是: 如果有人想知道这个问题,因为他们真的想在实践中使用它,我已经通过定义一个接口来解决这个问题,该接口包含我正在处理的所有接口和类中的所有方法的联合。也就是说,我试图做到以下几点:

class A {}


interface B {}


List<? extends A & B> list;

这是违法的,所以我这样做了:

class A {
<A methods>
}


interface B {
<B methods>
}


interface C {
<A methods>
<B methods>
}


List<C> list;

这仍然不如输入 List<? extends A implements B>有用,例如,如果有人向 A 或 B 添加或删除方法,列表的输入将不会自动更新,它需要手动更改为 C。但这对我有用。