81

我知道 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应该明确定义的语义——我想不出任何类型安全性的损失,因为允许两种类型的交集而不仅仅是一种。不过我敢肯定有一个解释。有谁知道它是什么?

4

5 回答 5

43

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

任何当前的推理引擎都必须进行大修才能允许这种分叉。但真正严重的问题是,这允许多种解决方案,但没有理由偏爱其中一种。

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

于 2011-07-11T03:19:19.593 回答
27

来自Java 语言规范

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

那么为什么不支持呢?我的猜测是,你应该怎么处理这样的事情?- 假设有可能:

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

那么应该怎么做

list.get(0);

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

于 2011-07-10T20:15:29.030 回答
12

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

这编译:

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!
}
于 2011-07-10T21:05:01.880 回答
2

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

让我们简化您的案例:您尝试执行相同的操作,就像您声明一个扩展 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 语言也不能。

于 2011-07-10T20:12:52.930 回答
-1

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

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。但它适用于我的需要。

于 2017-04-10T23:15:32.273 回答