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

dje*_*kyb 25 java generics casting raw-types unbounded-wildcard

原始列表转换为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;
}
Run Code Online (Sandbox Code Playgroud)

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

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

我试过了:

List<JAXBElement<?>> raw = api();
List<JAXBElement<?>> raw = (List<JAXBElement<?>>) api();
Run Code Online (Sandbox Code Playgroud)

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

有趣的是,这没有任何警告或错误:

for (JAXBElement<?> e : api()) {
    // ...
}
Run Code Online (Sandbox Code Playgroud)

Rad*_*def 29

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

// #2 (doesn't compile)
List<List> raw = null;
List<List<?>> wild = raw;
Run Code Online (Sandbox Code Playgroud)

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

#1被称为未经检查的转换:

存在未检查的转换从原始类或接口类型(§4.8)G的任何参数化的类型的形式的.G<T1,...,Tn>

具体来说,这是一个特殊情况的分配上下文仅适用于此场景:

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

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

这是为什么?嗯,这是由通用子类型规则特别规定的,更具体地说是这个要点:

给出一个通用的类型声明(Ñ > 0),则直接超参数化类型的,其中,(1≤ Ñ)是一种类型的,是以下所有:C<F1,...,Fn>C<T1,...,Tn>Ti

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

这将我们引用到JLS调用包含的内容,在哪里是有效的赋值,左侧的参数必须包含右侧的参数.遏制主要控制通用子类型,因为"具体"泛型类型不变的.

您可能熟悉以下想法:

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

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

所以问题变成"类型参数是否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();
Run Code Online (Sandbox Code Playgroud)

(你可以代替JAXBElement内心List.)

这种铸件的用例应该是安全的,因为它List<List<?>>是一种比限制更严格的类型List<List>.

  • 原始类型语句是拓宽投选中,然后分配.这是有效的,因为如上所示,任何参数化类型都可以转换为其原始类型,反之亦然.

  • 稍微安全的声明(如此命名是因为它失去了较少类型信息)是拓宽投则投收窄.这通过转换为常见的超类型来工作:

        List<? extends List>
            ?         ?
    List<List<?>>     List<List>
    
    Run Code Online (Sandbox Code Playgroud)

    有界通配符允许通过包含来考虑类型参数的子类型.

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

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

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

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

    (也就是说List<? extends List> :> List<? extends List<?>> :> List<List<?>>.)

  • 第三个示例的工作方式类似于第二个示例,通过转换为常用的超类型List<? super List<?>>.由于它不使用原始类型,我们可以抑制少一个警告.


这里的非技术性摘要是规范暗示在List<List>和之间既没有子类型也没有超类型关系List<List<?>>.

虽然转换List<List>List<List<?>>应该是安全的,但是不允许.(这是安全的,因为它们都List可以存储任何类型List,但是List<List<?>>对它们的元素在检索后如何使用会施加更多限制.)

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

  • 我非常感谢您在三段论叙述中参考 jls 所做的努力 (2认同)