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

Irregularities with the (?) wildcard generic type

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

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.

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

注:编译器Java1.8

您可以向 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 */);
}

所以实际上您无法对该列表中的元素做任何违反类型安全的事情(前提是您不做任何故意违反它的事情,例如使用原始类型)。您可以证明泛型 producer/consumer 方法同样安全或被编译器禁止。

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

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

一切都是为了 subtype/supertype-relationships。

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> 的原因相同:元素您要添加的是列表中预期元素的真实 子类型

I believe that the type ? in generics is a specific unknown type.

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

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

唯一不变的是,其类型包含通配符的表达式将始终生成其类型符合该通配符的值。由于每个值都有一个不仅仅是通配符的类型,所以可以说通配符在任何时候都代表(更多)"specific" 类型。