我有一个通用接口interface ListList<E> extends List<List<E>>
。由于某些原因,我无法将ListList<? super T>
强制转换为List<List<? super T>>
。有什么办法可以解决,为什么不起作用?
到目前为止,我已经尝试了以下方法:
ListList<? super T>
分配给List<? extends List<? super T>>
(1),但是当我尝试将ListList<? super T>
分配给List<List<? super T>>
时,我得到了{{ 1}}编译时错误(1.1)。Incompatible types
编译时错误(2),它不起作用。Incompatible types
,它可以正常工作(3),但我不喜欢原始类型。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>>
。
答案 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
等于List
个List
中的一个。但是,您要做的是获取一个嵌套的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
您的ListList
与List
的{{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>
即可避免通配符捕获。