为什么列表列表 <?超级 E> 是列表 <?扩展列表 <?超级 E>> 但不是 List<List<? super E>>

Why ListList<? super E> is List<? extends List<? super E>> but not List<List<? super E>>

我有一个通用接口 interface ListList<E> extends List<List<E>>。由于某些原因,我无法将 ListList<? super T> 转换为 List<List<? super T>>。有什么方法可以做到吗?为什么它不起作用?

到目前为止,我已经尝试了以下方法:

  1. 简单赋值,这样我就成功地将 ListList<? super T> 赋给了 List<? extends List<? super T>> (1),但是当我尝试将 ListList<? super T> 赋给 List<List<? super T>> 时,我得到了Incompatible types 编译时错误 (1.1)。
  2. 显式类型转换,由于相同的 Incompatible types 编译时错误 (2),它不起作用。
  3. 转换为原始类型 ListList,它有效 (3),但我不喜欢原始类型。
  4. 添加从 ListList<? super T>List<? extends List<? super T>> 的所有元素,它有效 (4),但我需要一个更通用的解决方案,它不仅适用于 ListList<E>,而且适用于任何通用类型.

这是我的代码:

ListList<? super T> var = new ArrayListList<>();
List<? extends List<? super T>> work = var; // (1)
List<List<? super T>> notWork = var; // (1.1)
List<List<? super T>> explicit = (List<List<? super T>>) var; // (2)
List<List<? super T>> raw = (ListList) var; // (3)
List<List<? super T>> copy = new ArrayList<>(); // (4)
copy.addAll(var); // (4)

我原以为 ListList<? super T>List<List<? super T>>,但它似乎是 List<? extends List<? super T>>。我需要知道为什么会这样,以及如何在没有原始类型和元素复制的情况下将其转换为 List<List<? super T>>

起初,看起来这些赋值应该都成功,但由于内部通配符 ? super T 而没有成功。如果我们删除那些通配符,那么所有的分配都会编译。

public static <T> void test() {
    ListList<T> var = new ArrayListList<>();
    List<? extends List<T>> work = var; // Compiles
    List<List<T>> notWork = var; // Compiles
    List<List<T>> explicit = (List<List<T>>) var; // Compiles
    List<List<T>> raw = (ListList) var; // Compiles with warning
    List<List<T>> copy = new ArrayList<>(); // Compiles
    copy.addAll(var); // Compiles
}

我仍然收到 (3) 的未经检查的转换警告,但它们仍然可以编译。

乍一看像是在声明接口

ListList<E> extends List<List<E>>

使 ListList 等同于 ListList。但是,您所做的是采用嵌套类型参数并将其作为主要类型参数。造成差异的原因是 nested wildcards don't perform wildcard capture.

此处的嵌套通配符表示 "a list of lists of any type matching the bound",但此处的 main-level 通配符表示 "a 'listlist' of a specific yet unknown type matching the bound".

不能将作为下限超类型的对象添加到集合中,因为类型参数——一个具体但未知的类型——可能是实际的界限.

List<? super Integer> test2 = new ArrayList<>();
test2.add(2);   // Compiles; can add 2 if type parameter is Integer, Number, or Object
test2.add((Number) 2);   // Error - Can't add Number to what could be Integer
test2.add(new Object()); // Error - Can't add Object to what could be Integer

因为Java的泛型是不变的,涉及类型参数时类型必须完全匹配,所以ListList的类似情况都无法编译。

// My assumption of how your ArrayListList is defined.
class ArrayListList<E> extends ArrayList<List<E>> implements ListList<E> {}

ListList<? super Integer> llOfSuperI = new ArrayListList<>();
llOfSuperI.add(new ArrayList<Integer>());  // capture fails to match Integer
llOfSuperI.add(new ArrayList<Number>());   // capture fails to match Number
llOfSuperI.add(new ArrayList<Object>());   // capture fails to match Object

但是,ListList 可以编译所有 3 种情况。

List<List<? super Integer>> lOfLOfSuperI = new ArrayList<>();
lOfLOfSuperI.add(new ArrayList<Integer>());  // no capture; compiles
lOfLOfSuperI.add(new ArrayList<Number>());   // no capture; compiles
lOfLOfSuperI.add(new ArrayList<Object>());   // no capture; compiles

您的 ListListListList 是不同的类型,但是类型参数定义位置的不同泛型行为意味着存在不同的泛型行为。这就是为什么你不能直接将 ListList<? super T> 分配给 List<List<? super T>> (1.1),也是你不能强制转换它的原因 (2)。您可以转换为原始类型以使其编译 (3),但这会在将来使用转换对象时引入 ClassCastException 的可能性;这就是警告的内容。您可以将其分配给一个 List<? extends List<? super T>> (1),引入另一个通配符来捕获子类型关系,但是引入一个要捕获的通配符;您将无法向该列表添加任何有用的内容。

出现这些差异只是因为通配符引入了通配符捕获和相关差异。在不使用通配符的情况下,ListList<E> 等同于 List<List<E>>,如本答案顶部所示,编译代码没有问题。

如果您希望所有 sub-lists 使用完全相同的类型参数,请继续使用您的 ListList 接口,但不要使用任何通配符。这会为添加到 ListList 的所有列表强制使用完全相同的类型参数,即 ListList<Integer> 只能容纳 List<Integer>s.

如果您希望所有 sub-lists 只匹配通配符,例如在同一列表中包含 List<Number>List<Integer>List<Object>,然后只需使用 List<List<? super T>> 即可避免通配符捕获。