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

时间:2011-07-10 19:00:07

标签: java generics language-design bounded-wildcard

我知道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的语义应该是明确定义的 - 我不能通过允许两种类型的交集来考虑类型安全性的任何损失而不仅仅是一种。我确定有一个解释。有谁知道它是什么?

5 个答案:

答案 0 :(得分:34)

有趣的是,接口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.

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

你的问题的固定答案是,仿制药已经太复杂了;增加更多的复杂性可能是最后一根稻草。

要允许通配符具有多个上限,必须扫描规范并确保整个系统仍然有效。

我知道的一个问题是类型推断。当前的推理规则根本无法处理拦截类型。减少约束A&B << C没有规则。如果我们把它减少到

    A<<C 
  or
    A<<B

任何当前的推理引擎都必须经过大修以允许这样的分叉。但真正严重的问题是,这允许多种解决方案,但没有理由偏爱另一种。

然而,推断对于打字安全并不重要;在这种情况下,我们可以简单地拒绝推断,并要求程序员明确填写类型参数。因此,推理难度不是反对拦截类型的强烈论据。

答案 1 :(得分:24)

来自Java Language Specification

  

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

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

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

那应该是什么

list.get(0);

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

答案 2 :(得分:10)

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

编译:

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

答案 3 :(得分:1)

好问题。我花了一段时间才弄明白。

让我们简化你的情况:你试图做同样的事情,就像你声明一个扩展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语言也不能。

答案 4 :(得分:0)

对于它的价值:如果有人想知道这是因为他们真的想在实践中使用它,我通过定义一个包含所有接口和类中所有方法的联合的接口来解决它与...合作。即我试图做以下事情:

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.但它可以满足我的需求。