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

原始列表转换为刚刚好。为什么原始列表列表不能转换为列表?List<?>List<?>

{   // 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;
}

背景故事(缓解 xy 问题):

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

我试过了:

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

但这些给出类型不匹配错误。

有趣的是,这不会给出任何警告或错误:

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

答案 1
// #1 (does compile)
List raw = null;
List<?> wild = raw;

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

首先,让我们弄清楚为什么这些实际上是不相关的任务。也就是说,它们受不同的规则约束。

#1 称为未经检查的转化

存在从原始类或接口类型 (§4.8) 到表单的任何参数化类型的未经检查的转换GG<T1,...,Tn>

具体来说,它是仅针对此方案的赋值上下文的一个特例:

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

#2 需要引用类型转换;然而,它的问题在于它不是一个扩大的转换(这是那种在没有强制转换的情况下隐式允许的引用转换)。

为什么?好吧,这特别受通用子类型规则的约束,更具体地说是这个要点:

给定一个泛型类型声明 (n > 0),参数化类型的直接超类型 (其中 (1 ≤ in) 是一个类型,它们都如下:C<F1,...,Fn>C<T1,...,Tn>Ti

  • C<S1,...,Sn>,其中包含 (1 ≤ in)。SiTi

这向我们引用了JLS调用的包含,其中要成为有效的赋值,左侧的参数必须包含右侧的参数。包含在很大程度上控制泛型子类型,因为“具体”泛型类型不变的

您可能熟悉以下想法:

  • a 不是 aList<Dog>List<Animal>
  • 但 a 是 .List<Dog>List<? extends Animal>

好吧,后者是正确的,因为包含.? extends AnimalDog

所以问题变成了“类型参数List<>包含原始类型参数List”?答案是否定的:虽然 是 的子类型,但这种关系对于类型参数不成立。List<?>List

没有特殊的规则可以使它成为事实:不是 的子类型,因为本质上相同的原因不是 的子类型。List<List<?>>List<List>List<Dog>List<Animal>

所以 因为 不是 的子类型,所以赋值是无效的。同样,您不能执行直接缩小转换强制转换,因为两者都不是超类型。List<List>List<List<?>>List<List>List<List<?>>


要进行分配,您仍然可以应用强制转换。在我看来,有三种方法是合理的。

// 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();

(您可以替换内部 。JAXBElementList

此强制转换的用例应该是安全的,因为它是比 限制性更强的类型。List<List<?>>List<List>

  • 原始类型语句是加宽强制转换,然后不受检查的赋值。这是有效的,因为如上所述,任何参数化类型都可以转换为其原始类型,反之亦然。

  • 稍微安全一点的声明(这样命名,因为它丢失的类型信息较少)是加宽转换,然后缩小转换。这可以通过转换为通用超类型来工作:

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

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

    被认为是超型的事实可以用传递性来证明:List<? extends List>List<List<?>>

    1. ? extends List包含 ,因为 是 的超类型。? extends List<?>ListList<?>

    2. ? extends List<?>包含。List<?>

    3. 因此包含 .? extends ListList<?>

    (即。)List<? extends List> :> List<? extends List<?>> :> List<List<?>>

  • 第三个示例的工作方式与第二个示例类似,方法是转换为通用超类型 。由于它不使用原始类型,因此我们可以减少一个警告。List<? super List<?>>


这里的非技术性总结是,规范意味着 和 之间既没有子类型也没有超类型关系。List<List>List<List<?>>

尽管从 转换为 应该是安全的,但不允许这样做。(这是安全的,因为两者都是可以存储任何类型的 ,但是a对其元素在检索后如何使用它们施加了更多限制。List<List>List<List<?>>ListListList<List<?>>

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


答案 2

不能直接分配或强制转换它,因为原始类型与 不同ListList<?>

使用类型检查时将被忽略,您可以对任何类型使用任何泛型方法。使用编译器时,不允许您使用具有泛型参数的方法ListList<?>


因此,您可以忽略以下警告:

@SuppressWarnings("rawtypes")

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

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

推荐