(?)通配符泛型类型的不规则

时间:2016-08-18 09:27:08

标签: java generics unbounded-wildcard

我认为泛型中的?类型是特定的未知类型。这意味着,声明让我们说这种类型的列表会阻止我们在其中添加任何类型的对象。

List<?> unknownList;
unknownList.add(new Object()); // This is an error.

编译器按预期给出错误。

但是当未知类型是二级泛型时,编译器似乎并不关心。

class First<T> {}

List<First<?>> firstUnknownList;

// All these three work fine for some reason.
firstUnknownList.add(new First<>());
firstUnknownList.add(new First<Integer>());
firstUnknownList.add(new First<String>());

我认为编译器可能根本不关心二级通用参数,但情况并非如此,

List<First<Integer>> firstIntegerList;
firstIntegerList.add(new First<String>()); // This gives a compiler error as expected.

那么,为什么编译器允许我们在第二个例子中只接受一个未知元素(因而没有任何东西)时添加任何类型的元素?

注意:编译器Java 1.8

4 个答案:

答案 0 :(得分:13)

您可以向List<T>添加任何可以存储在T类型的引用中的内容:

T item = ...
List<T> list = new ArrayList<>();
list.add(item);

First<?>First<T>的超类型;因此,您可以在First<T>类型的变量中存储对First<?>的引用:

First<?> first = new First<String>();

所以,用T替换上面的First<?>

First<?> item = new First<String>();
List<First<?>> list = new ArrayList<>();
list.add(item);

OP的例子中发生的一切是省略了临时变量item

firstUnknownList.add(new First<String>());

但是,如果您使用firstIntegerList示例执行此操作:

First<Integer> item = new First<String>(); // Compiler error.
List<First<Integer>> list = new ArrayList<>();
list.add(item);

很清楚为什么不允许这样做:你无法分配item

也可以看到你不能对该列表的内容做任何不安全

如果向界面添加几种方法:

interface First<T> {
  T producer();
  void consumer(T in);
}

现在,考虑一下您可以对添加到列表中的元素执行的操作:

for (First<?> first : firstUnknownList) {
  // OK at compile time; OK at runtime unless the method throws an exception.
  Object obj = first.producer();

  // OK at compile time; may fail at runtime if null is not an acceptable parameter.
  first.consumer(null);

  // Compiler error - you can't have a reference to a ?.
  first.consumer(/* some maybe non-null value */);
}

因此,实际上没有任何东西可以对该列表的元素进行真正的违反类型安全的行为(前提是你没有做任何违反它的任何事情,比如使用原始类型)。您可以证明编译器同样安全或禁止通用生产者/消费者方法。

所以没有理由允许你这样做。

答案 1 :(得分:4)

我将First接口更改为Box接口

带有内容的

Box<?> uknownBox灰色框

带苹果的

Box<Apple> appleBox

List<Box<Apple>> appleBoxList许多带苹果的盒子

List<Box<?>> uknownBoxList许多未知的灰色框

appleBoxList.add(new Box<Orange>()) - 无法将橙色框添加到苹果框列表中

unknownBoxList.add(new Box<?>()) - 我们不知道灰色框中的内容,添加一个未知的灰色框不会改变任何内容

unknownBoxList.add(new Box<Orange>()) - same rules when you add specific boxes
unknownBoxList.add(new Box<Apple>()) - since you are not allowed to 'open' them

unknownBoxList = appleBoxList这不会编译以防止将灰色(可能不是苹果)框添加到Apple框列表中。因为以前的操作是合法的。

答案 2 :(得分:4)

所有关于子类型/超类型关系。

List<?>是一个包含未知(但特定)类型元素的列表。您永远不知道此列表中包含完全的类型。所以你可能不会向它添加对象,因为它们可能是错误的类型:

List<Integer> ints = new ArrayList<Integer>();
List<?> unknowns = ints;

// It this worked, the list would contain a String....
unknowns.add("String"); 

// ... and this would crash with some ClassCastException
Integer i = ints.get(0);

你也可以做到

List<Number> numbers = null;
Integer integer = null;
numbers.add(integer); 

这是有效的,因为NumberInteger的真正超类型。将更具体类型的对象添加到列表中不会违反类型安全性。

关于第二个例子的关键点是:

First<?>是每个First<T>

的超类型

您可以随时使用

First<Integer> fInt = null;
First<Integer> fString = null;

First<?> f = null;
f = fInt; // works
f = fString; // works

因此,您可以向First<String>添加List<First<?>>的原因与您向Integer添加List<Number>的原因相同:您的元素想要添加列表中预期的元素的真正子类型

答案 3 :(得分:0)

  

我相信那种?在泛型中是一种特定的未知类型。

这有点不准确。是的,通配符类型代表未知类型,但在不同时间可能代表不同类型:

List<?> list = new ArrayList<String>();
list = new ArrayList<Integer>();

唯一的不变量是类型包含通配符的表达式将始终生成一个类型符合该通配符的值。由于每个值都有一个不仅仅是通配符的类型,因此可以说通配符代表(更多)&#34;特定的&#34;随时输入。