为什么你不能有一个有界的通配符通用的多个接口?

我知道Java的genericstypes有各种反直觉的属性。 这里有一个特别的,我不明白,我希望有人可以向我解释。 为类或接口指定types参数时,可以将其绑定,以便它必须实现具有public class Foo<T extends InterfaceA & InterfaceB>多个接口。 但是,如果你正在实例化一个实际的对象,这是行不通的。 List<? extends InterfaceA> List<? extends InterfaceA>是好的,但List<? extends InterfaceA & InterfaceB> 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的语义应该是明确的 – 我不能想到types安全的任何损失,通过允许两种types的交集,而不是一个。 我确定有一个解释。 有人知道它是什么吗?

有趣的是,接口java.lang.reflect.WildcardType看起来像支持通配符arg的上下界一样; 每个可以包含多个边界

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

这超出了语言允许的范围。 源代码中有一个隐藏的评论

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

界面的作者似乎认为这是一个意外的限制。

对于你的问题的答案是,generics已经太复杂了。 增加更多的复杂性可能被certificate是最后一根稻草。

为了让通配符有多个上限,必须扫描规范并确保整个系统仍然有效。

我知道的一个麻烦是types推理。 目前的推断规则根本无法处理截取types。 没有规则来减less约束A&B << C 如果我们减less到

  A<<C or A<<B 

任何当前的推理引擎都必须经过大的修改才能允许这样的分叉。 但真正严重的问题是,这允许多种解决scheme,但没有理由相互偏好。

但是,推论对于types安全来说并不重要。 在这种情况下,我们可以简单地拒绝推断,并要求程序员明确填写types参数。 因此,推断困难并不是一个反对截获types的有力论据。

从Java语言规范 :

4.9交集types交集types的forms为T1&…&Tn,n> 0,其中Ti,1in是typesexpression式。 交集types出现在捕获转换(§5.1.10)和types推断(§15.12.2.7)的过程中。 作为程序的一部分直接写交集types是不可能的; 没有语法支持这一点 。 交集types的值是1in的所有typesTi的值的那些对象。

那么为什么这不被支持? 我的猜测是,你应该怎么做这样的事情? – 让我们假设这是可能的:

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

那该怎么办

 list.get(0); 

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

没问题…只要在方法签名中声明你需要的types。

这编译:

 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! } 

好问题。 我花了一段时间才弄清楚。

让我们简化你的情况:你试图做同样的事情,就好像你声明了一个扩展2个接口的类,然后是一个具有2个接口types的variables,如下所示:

  class MyClass implements Int1, Int2 { } Int1 & Int2 variable = new MyClass() 

当然是非法的。 这相当于你用generics来做什么。 你想要做的是:

  List<? extends A & B> foobar; 

但是,为了使用foobar, 你需要这样使用两个接口的variables

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

这在Java中是不合法的。 这意味着,你将这个列表中的元素同时声明为两种types的元素, 即使我们的大脑可以处理它,Java语言也不能。

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

 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; 

这还不如能够inputList<? extends A implements B> List<? extends A implements B> ,例如,如果有人向A或B添加或删除方法,列表的打字将不会自动更新,它需要手动更改为C.但它是为我的需要工作。