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

2022-09-03 01:43:02

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

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


答案 1

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

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

First<?>是 的超类型 ;所以你可以在一个变量中存储对 的引用::First<T>First<T>First<?>

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

因此,替换上述内容:TFirst<?>

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 */);
}

因此,对于该列表中会违反类型安全的元素,您实际上无法真正执行任何操作(前提是您不做任何故意违反它的事情,例如使用原始类型)。您可以证明泛型生产者/使用者方法同样安全或被编译器禁止。

因此,没有理由不允许您这样做。


答案 2

这一切都与子类型/超类型关系有关。

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<T的超类型>

你总是可以像这样的东西

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

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

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


推荐