17

我相信?泛型中的类型是特定的未知类型。这意味着,声明该类型的列表将阻止我们向其中添加任何类型的对象。

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

4 回答 4

13

您可以将任何内容添加到List<T>您可以存储在 type 的引用中T

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

First<?>是 的超First<T>类型 因此您可以将对 a 的引用存储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 */);
}

因此,实际上您无法对该列表中的元素进行任何违反类型安全的操作(前提是您没有故意违反它,例如使用原始类型)。您可以证明通用生产者/消费者方法同样安全或被编译器禁止。

所以没有理由不让你这样做。

于 2016-08-18T10:44:52.070 回答
4

我将界面更改FirstBox界面

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这不会编译以防止将灰色(可能不是苹果)框添加到苹果框列表。因为之前的操作是合法的。

于 2016-08-18T11:16:50.530 回答
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); 

Number是有效的,因为它是Integer. 将更具体类型的对象添加到列表中不会违反类型安全。


第二个例子的关键点是:

First<?>是每个的超类型First<T>

你总是可以像

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

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

因此,您可以将 a 添加First<String>到 a的原因与您可以将 an 添加到 aList<First<?>>的原因相同:您要添加的元素是列表中预期元素的真正子类型。IntegerList<Number>

于 2016-08-18T11:27:23.863 回答
0

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

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

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

唯一不变的是类型包含通配符的表达式将始终产生一个类型符合该通配符的值。由于每个值都有一个不仅仅是通配符的类型,因此可以说通配符在任何时候都代表(更多)“特定”类型。

于 2016-08-20T18:34:47.417 回答