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

时间:2019-07-01 18:12:38

标签: 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>>时,我得到了{{ 1}}编译时错误(1.1)。
  2. 显式类型转换,由于相同的Incompatible types编译时错误(2),它不起作用。
  3. 强制转换为原始类型Incompatible types,它可以正常工作(3),但我不喜欢原始类型。
  4. 将所有元素从ListList添加到ListList<? super T>,它可以正常工作(4),但是我需要一个更通用的解决方案,该解决方案不仅适用于List<? extends List<? super T>>,而且适用于任何通用类型。 / li>

这是我的代码:

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>>

1 个答案:

答案 0 :(得分: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)的未经检查的转换警告,但它们仍然可以编译。

乍一看,好像是在声明接口

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

使ListList等于ListList中的一个。但是,您要做的是获取一个嵌套的type参数并将其作为main type参数。产生差异的原因是nested wildcards don't perform wildcard capture

这里的嵌套通配符表示“与边界匹配的 any 类型的列表的列表”,但是在此处,主级别的通配符表示“ 特定但未知类型的'列表列表” 匹配边界”。

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

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

但是,List中的List会编译所有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

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

仅由于通配符引入了通配符捕获和相关联的差异,所以出现了这些差异。如果不使用通配符,则List<? extends List<? super T>>等效于ListList<E>,如该答案顶部所示,在编译代码时没有问题。

如果您希望所有子列表都使用相同的确切类型参数,请继续使用List<List<E>>接口,但不要使用任何通配符。这将对添加到ListList的所有列表强制使用完全相同的类型参数,即ListList仅可容纳ListList<Integer>个。

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