为什么ListList<? super E>是List<? extends List<? super E>>而不是List<List<? super E>>?

6
我有一个通用接口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>>时,我得到了编译时错误“不兼容的类型”(1.1)。
  2. 显式类型转换,由于相同的编译时错误,它不起作用(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 更改为普通的 T 才能使其编译。如果我将它们更改为 ? extends T,则相同的错误会再次出现。 - rgettman
1个回答

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
}

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

乍一看,似乎是声明了接口

Original Answer翻译成"最初的回答"

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

使一个 ListList 等价于 ListList。但是你所做的是将嵌套类型参数变成了主要类型参数。这样做的原因是因为嵌套通配符不执行通配符捕获
这里的嵌套通配符意味着“匹配绑定的任何类型的列表”,但是主级别通配符意味着“一个匹配绑定的特定但未知类型的'listlist'”。
不能向集合中添加低界限的超类型对象,因为类型参数——一个特定但未知的类型——可能是实际的边界。
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的情况都无法编译。

涉及到类型参数时,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

然而,一个包含多个列表的列表可以适用于这三种情况。"最初的回答"
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

你的ListList是不同于一个List<List>的类型,但是由于泛型参数定义的不同行为,有着不同的泛型行为。这就是为什么你不能直接将ListList<? super T>赋值给List<List<? super T>>(1.1),也为什么你不能将其强制转换(2)。你可以将其转换为原始类型以使其编译(3),但这会在将来使用转换后的对象时引入ClassCastException的可能性;这就是警告所指的内容。你可以将其分配给List<? extends List<? super T>>(1),引入另一个通配符来捕获子类型关系,但这会引入一个需要被捕获的通配符;你无法向该列表中添加任何有用的内容。
这些差异之所以出现,仅因为通配符引入了通配符捕获及其相关差异。没有使用通配符,ListList<E>等效于List<List<E>>,并且如本答案顶部所示,编译代码不会出现问题。
如果你想让所有的子列表使用完全相同的类型参数,则可以使用你的ListList接口,但不要使用任何通配符。这会强制对添加到你的ListList中的所有列表使用完全相同的类型参数,即ListList<Integer>只能容纳List<Integer>
如果你希望所有子列表仅匹配一个通配符,例如,在同一列表中包含List<Number>List<Integer>List<Object>,则只需使用List<List<? super T>>避免通配符捕获。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接