为什么使用ListList <?超级E>是List <?扩展List <?超级E >>,但不是List <List <?超级E >>

Ban*_*non 6 java generics casting wildcard lower-bound

我有一个通用接口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)
Run Code Online (Sandbox Code Playgroud)

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

rge*_*man 2

乍一看,这些分配似乎都应该成功,但由于内部通配符的原因,它们没有成功? 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
}
Run Code Online (Sandbox Code Playgroud)

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

乍一看好像是声明接口

ListList<E> extends List<List<E>>
Run Code Online (Sandbox Code Playgroud)

使 aListList等于sList的a List。但是,您所做的是采用嵌套类型参数并将其设为主要类型参数。造成差异的原因是嵌套通配符不执行通配符捕获

这里的嵌套通配符意味着“与绑定匹配的任何类型的列表的列表”,但是这里的主级通配符意味着“与绑定匹配的特定但未知类型的“列表列表””。

人们无法将作为下界超类型的对象添加到集合中,因为类型参数(一种特定但未知的类型)可能是实际的界限。

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
Run Code Online (Sandbox Code Playgroud)

由于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
Run Code Online (Sandbox Code Playgroud)

然而,a ListofList可以对所有 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
Run Code Online (Sandbox Code Playgroud)

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

这些差异的出现只是因为通配符引入了通配符捕获和相关差异。如果不使用通配符,aListList<E>相当于 a,List<List<E>>并且如本答案顶部所示,显示编译代码没有问题。

如果您希望所有子列表都使用相同的确切类型参数,请继续使用您的ListList接口,但不要使用任何通配符。这会强制添加到 的所有列表使用完全相同的类型参数ListList,即 aListList<Integer>只能保存List<Integer>s。

如果您希望所有子列表都简单地匹配通配符,例如在同一列表中包含List<Number>, List<Integer>, 和,则只需使用 a来避免通配符捕获。List<Object>List<List<? super T>>