28

原始列表转换为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()) {
    // ...
}
4

3 回答 3

34
// #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 称为包含的东西,要成为有效赋值,左侧的参数必须包含右侧的参数。包含主要控制泛型子类型,因为“具体”泛型类型不变的。

您可能熟悉以下想法:

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

好吧,后者是正确的,因为? extends Animal contains 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();

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

您的这种转换的用例应该是安全的,因为List<List<?>>它是一种比List<List>.

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

  • 稍微安全一点的语句(这样命名是因为它丢失的类型信息较少)是一个加宽转换然后缩小转换。这通过转换为一个常见的超类型来工作:

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

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

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

    1. ? extends Listcontains ? 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>to转换List<List<?>>应该是安全的,但这是不允许的。(这是安全的,因为两者都是 aList可以存储任何类型的List,但是 aList<List<?>>对其元素在检索后的使用方式施加了更多限制。)

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

于 2014-11-05T22:30:04.083 回答
3

您不能直接分配或强制转换它,因为原始类型List List<?>.

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


因此,您可以忽略警告:

@SuppressWarnings("rawtypes")

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

List<JAXBElement<String>> raw = (List<JAXBElement<String>>) ((Object)api());
于 2014-11-05T20:52:18.980 回答
0

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

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

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

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

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

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

于 2014-11-05T20:50:20.080 回答