无法从List <list>转换为List <list <?>&gt; </list <?> </list>

时间:2014-11-05 20:41:49

标签: 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;
}

Backstory (以缓解the xy problem):

我正在使用的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()) {
    // ...
}

3 个答案:

答案 0 :(得分:29)

// #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.8GG<T1,...,Tn>形式的任何参数化类型都有未经检查的转化

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

  

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

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

为什么?嗯,这具体由rules of generic subtyping管理,更具体地说是这个要点:

  

给定泛型类型声明C<F1,...,Fn> n &gt; 0),参数化类型C<T1,...,Tn>直接超类型,其中{{ 1}}(1≤ i n )是一种类型,全部如下:

     
      
  • Ti,其中C<S1,...,Sn>包含Si(1≤ i n )。
  •   

这引用了我们JLS调用containment的内容,哪里是有效的赋值,左侧的参数必须包含右侧的参数。由于"concrete" generic typesinvariant,因此遏制主要支配通用子类型。

您可能熟悉以下观点:

  • Ti不是List<Dog>
  • List<Animal>List<Dog>

嗯,后者是真的,因为List<? extends Animal> 包含 ? extends Animal

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

没有特殊规则可以使其成立:List不是List<List<?>>的子类型,原因基本相同List<List>不是List<Dog>的子类型。< / p>

因为List<Animal>不是List<List>的子类型,所以分配无效。同样,您无法执行直接narrowing conversion转换,因为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>的超类型这一事实可以通过及物证明:

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

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

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

    4. (即List<?>。)

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


此处的非技术性摘要是规范暗示List<? super List<?>>List<List>之间既没有子类型也没有超类型关系。

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

遗憾的是,由于原始类型很奇怪并且使用它们存在问题,因此没有实际原因导致无法编译。

答案 1 :(得分:3)

您无法直接指定或投放,因为原始类型List isn't the sameList<?>

使用List时,忽略类型检查,您可以使用任何类型的通用方法。使用List<?>compiler won't let you use methods with generic parameters


因此您可以忽略警告:

@SuppressWarnings("rawtypes")

和/或使用解决方法明确地转换它:

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

答案 2 :(得分:0)

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

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

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

  

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

     

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

     

Box stringBox = new Box&lt;&gt;(); Box rawBox = stringBox;   rawBox.set(8); //警告:取消选中要调用的设置(T)警告   显示原始类型绕过泛型类型检查,推迟捕获   不安全的代码到运行时。因此,您应该避免使用原始   类型。