无法从 List<List> 转换为 List<List<?>>

Posted

技术标签:

【中文标题】无法从 List<List> 转换为 List<List<?>>【英文标题】:Cannot convert from List<List> to List<List<?>> 【发布时间】:2014-11-05 20:41:49 【问题描述】:

原始列表转换为List&lt;?&gt; 就好了。为什么原始列表的列表不能转换为 List&lt;?&gt; 的列表?

   // works
    List raw = null;
    List<?> wild = raw;

   // Type mismatch: cannot convert from List<List> to List<List<?>>
    List<List> raw = null;
    List<List<?>> wild = raw;

背景故事(缓解the xy problem):

我正在使用的 API 返回 List&lt;JAXBElement&gt;。我碰巧知道它总是List&lt;JAXBElement&lt;String&gt;&gt;。我计划循环并构建我自己的List&lt;String&gt;,但我在编写List&lt;JAXBElement&gt; raw = api(); 时试图修复(但不抑制)原始类型编译器警告。

我试过了:

List<JAXBElement<?>> raw = api();
List<JAXBElement<?>> raw = (List<JAXBElement<?>>) api();

但这些会导致类型不匹配错误。

有趣的是,这没有给出警告或错误:

for (JAXBElement<?> e : api()) 
    // ...

【问题讨论】:

如果你知道它总是List&lt;JAXBElement&lt;String&gt;&gt;,请转换成那个,而不是List&lt;?&gt;List&lt;JAXBElement&lt;?&gt;&gt;。如果您知道所有类型,请远离问号。 如果你只想删除警告,你可以使用@SuppressWarnings("rawtypes") 如果你知道它是List&lt;JAXBElement&lt;String&gt;&gt;,你可以通过原始类型强制强制转换。 (List&lt;JAXBElement&lt;String&gt;&gt;)(List)api() @djeikyb 我编辑了我的答案,请检查一下,你会发现你无法避免警告。 这是因为 "unknown" != "anything" 【参考方案1】:
// #1 (does compile)
List raw = null;
List<?> wild = raw;

// #2 (doesn't compile)
List<List> raw = null;
List<List<?>> wild = raw;

首先让我们理清为什么这些实际上是不相关的作业。也就是说,它们受不同的规则管辖。

#1 被称为unchecked conversion:

存在从原始类或接口类型 (§4.8) GG&lt;T<sub>1</sub>,...,T<sub>n</sub>&gt; 形式的任何参数化类型的未经检查的转换

具体来说,它是 assignment context 的特例,仅适用于这种情况:

如果在应用 [其他可能的转换] 之后,结果类型是原始类型,则可能会应用未经检查的转换。

#2 需要引用类型转换;但是它的问题在于它不是widening conversion(这是一种在没有强制转换的情况下隐式允许的引用转换)。

这是为什么呢?嗯,这是由rules of generic subtyping 尤其是这个要点特别管理的:

给定一个泛型类型声明C&lt;F<sub>1</sub>,...,F<sub>n</sub>&gt; (n > 0),参数化类型C&lt;T<sub>1</sub>,...,T<sub>n</sub>&gt;直接超类型,其中T<sub>i</sub> (1 ≤ i ≤ n) 是一个类型,都是以下几种:

C&lt;S<sub>1</sub>,...,S<sub>n</sub>&gt;,其中S<sub>i</sub> 包含T<sub>i</sub> (1 ≤ in)。

这指的是 JLS 称为 containment 的东西,要成为有效赋值,左侧的参数必须包含右侧的参数。包含主要控制通用子类型,因为"concrete" generic types 是invariant。

您可能熟悉以下想法:

List&lt;Dog&gt; 不是List&lt;Animal&gt;List&lt;Dog&gt;List&lt;? extends Animal&gt;

嗯,后者是真的,因为? extends Animal 包含 Dog

所以问题变成了“类型参数List&lt;?&gt; 是否包含原始类型参数List?答案是否定的:虽然List&lt;?&gt;List 的子类型,但这种关系不适用于类型参数。

没有特殊的规则可以证明这一点:List&lt;List&lt;?&gt;&gt; 不是List&lt;List&gt; 的子类型,原因基本相同,List&lt;Dog&gt; 不是List&lt;Animal&gt; 的子类型。

所以因为List&lt;List&gt; 不是List&lt;List&lt;?&gt;&gt; 的子类型,所以赋值无效。同样,您不能执行直接的narrowing conversion 转换,因为List&lt;List&gt; 也不是List&lt;List&lt;?&gt;&gt; 的超类型。


要进行分配,您仍然可以应用演员表。在我看来,有三种方法可以做到这一点。

// 1. raw type
@SuppressWarnings("unchecked")
List<List<?>> list0 = (List) api();

// 2. slightly safer
@SuppressWarnings("unchecked", "rawtypes")
List<List<?>> list1 = (List<List<?>>) (List<? extends List>) api();

// 3. avoids a raw type warning
@SuppressWarnings("unchecked")
List<List<?>> list2 = (List<List<?>>) (List<? super List<?>>) api();

(你可以用JAXBElement代替内部的List。)

此转换的用例应该是安全的,因为 List&lt;List&lt;?&gt;&gt; 是比 List&lt;List&gt; 更严格的类型。

原始类型 语句是一个扩大的转换然后是未经检查的分配。这是因为,如上所示,任何参数化类型都可以转换为其原始类型,反之亦然。

稍微安全语句(之所以这样命名是因为它丢失的类型信息较少)是一个加宽转换然后是窄化转换。这通过强制转换为一个共同的超类型来工作:

    List<? extends List>
        ╱         ╲
List<List<?>>     List<List>

有界通配符允许考虑通过包含进行子类型化的类型参数。

List&lt;? extends List&gt; 被认为是List&lt;List&lt;?&gt;&gt; 的超类型这一事实可以通过传递性来证明:

    ? extends List 包含? extends List&lt;?&gt;,因为ListList&lt;?&gt; 的超类型。

    ? extends List&lt;?&gt; 包含List&lt;?&gt;

    因此? extends List 包含List&lt;?&gt;

(即List&lt;? extends List&gt; :&gt; List&lt;? extends List&lt;?&gt;&gt; :&gt; List&lt;List&lt;?&gt;&gt;。)

第三个示例的工作方式与第二个示例类似,通过转换为通用超类型List&lt;? super List&lt;?&gt;&gt;。由于它不使用原始类型,我们可以减少一个警告。


这里的非技术总结是规范暗示List&lt;List&gt;List&lt;List&lt;?&gt;&gt;之间既没有子类型也没有超类型关系。

虽然从List&lt;List&gt; 转换为List&lt;List&lt;?&gt;&gt; 应该是安全,但这是不允许的。 (这是安全的,因为两者都是 List,可以存储任何类型的 List,但 List&lt;List&lt;?&gt;&gt; 对其元素在检索后的使用方式施加了更多限制。)

不幸的是,除了原始类型很奇怪并且它们的使用存在问题之外,没有任何实际原因无法编译。

【讨论】:

我非常感谢您为参考 jls 以及您的三段论叙述所做的努力【参考方案2】:

您不能直接分配或转换它,因为原始类型 List isn't the same 为 List&lt;?&gt;

当使用List 时,类型检查将被忽略,您可以使用任何类型的任何泛型方法。当使用List&lt;?&gt; 时compiler won't let you use methods with generic parameters。


因此,您可以忽略警告:

@SuppressWarnings("rawtypes")

和/或使用解决方法显式转换它:

List<JAXBElement<String>> raw = (List<JAXBElement<String>>) ((Object)api());

【讨论】:

【参考方案3】:

如果您只想删除警告,您可以使用@SuppressWarnings("rawtypes")。

基本上,问题在于编译器将原始类型视为原始对象先前的泛型,所以......“旧对象”不是“通用对象”,所以......你不能转换它们。

从官方文档中阅读: http://docs.oracle.com/javase/tutorial/java/generics/rawTypes.html

但是,如果将原始类型分配给参数化类型,则会得到 警告:

盒子 rawBox = new Box(); // rawBox 是 Box 的原始类型 盒子 intBox = rawBox; // 警告:未经检查的转换 You 如果您使用原始类型调用泛型方法,也会收到警告 在相应的泛型类型中定义:

Box stringBox = new Box();盒子 rawBox = stringBox; rawBox.set(8); // 警告:未经检查的调用 set(T) 警告 显示原始类型绕过泛型类型检查,推迟捕获 运行时的不安全代码。因此,您应该避免使用 raw 类型。

【讨论】:

以上是关于无法从 List<List> 转换为 List<List<?>>的主要内容,如果未能解决你的问题,请参考以下文章

Dart:尽管强制转换,但无法将 List<dynamic> 转换为 List<Map<String, dynamic>>

无法将 '(<expression error>)' 从 '<brace-enclosed initializer list>' 转换为 std::unique_ptr<

为啥我不能从 List<MyClass> 转换为 List<object>?

无法将 initializer_list 转换为 class<int>

无法将类型“System.Collections.Generic.List<model1>”隐式转换为“System.Collections.Generic.List<model2&

无法将 Dictionary<string, List<string>> 转换为 IReadOnlyDictionary<string, IReadOnlyCollect