无法从 List<List> 转换到列表&lt;列表&lt;?&gt;&gt; [英] Cannot convert from List&lt;List&gt; to List&lt;List&lt;?&gt;&gt;

查看:27
本文介绍了无法从 List<List> 转换到列表&lt;列表&lt;?&gt;&gt;的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

原始列表可以很好地转换为 List.为什么原始列表的列表不能转换为 List 的列表?

{//有效列表原始 = null;列表野生 = 生;}{//类型不匹配:无法从 List 转换到列表<列表<?>>列表<列表>原始 = 空;列表<列表<?>>野生 = 生;}

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

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

我试过了:

List>原始 = api();列表<JAXBElement<?>>raw = (List) api();

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

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

for (JAXBElement e : api()) {//...}

解决方案

//#1 (编译)列表原始 = null;列表野生 = 生;//#2(不编译)列表<列表>原始 = 空;列表<列表<?>>野生 = 生;

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

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

<块引用>

从原始类或接口类型 (§4.8) GG1 形式的任何参数化类型,...,Tn>.

具体来说,它是 分配上下文 仅适用于这种情况:

<块引用>

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

#2 需要引用类型转换;然而,它的问题在于它不是 扩展转换(这是一种无需强制转换即可隐式允许的引用转换).

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

<块引用>

给定一个泛型类型声明 C1,...,Fn> (n > 0),参数化类型的直接超类型C1,...,Tn>,其中 Ti (1 ≤ in) 是一个类型,是所有的以下:

  • C1,...,Sn>,其中 Si 包含 Ti (1 ≤ in).

这指的是 JLS 称为 包含,如果要成为有效赋值,左侧的参数必须包含右侧的参数.由于"concrete" 泛型不变量.

您可能熟悉以下想法:

  • a List 不是 List
  • 但是 ListList.

好吧,后者是正确的,因为 ? extends Animal contains Dog.

所以问题变成了类型参数 List 是否包含原始类型参数 List"?答案是否定的:尽管 ListList 的子类型,但这种关系不适用于类型参数.

没有特殊的规则使它成为现实:List> 不是 List 的子类型,原因基本相同List 不是 List 的子类型.

所以因为 List 不是 List 的子类型,所以赋值无效.同样,您不能直接执行 缩小转换 因为List 也不是List 的超类型.

<小时>

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

//1. 原始类型@SuppressWarnings("未选中")列表<列表<?>>list0 = (List) api();//2. 稍微安全一点@SuppressWarnings({"unchecked", "rawtypes"})列表<列表<?>>list1 = (List>) (List) api();//3. 避免原始类型警告@SuppressWarnings("未选中")列表<列表<?>>list2 = (List>) (List>) api();

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

此转换的用例应该是安全的,因为 List> 是比 List 更具限制性的类型.>

  • raw type 语句是一个扩展转换,然后是未经检查的赋值.这是有效的,因为如上所示,任何参数化类型都可以转换为其原始类型,反之亦然.

  • 稍微安全一些语句(这样命名是因为它丢失的类型信息较少)是先扩大转换再缩小转换.这是通过强制转换为通用超类型来实现的:

     列表╱╲列表<列表<?>>列表<列表>

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

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

    1. ? extends List 包含 ? extends List,因为 List 是一个超类型List.

    2. ? extends List 包含 List.

    3. 因此 ? extends List 包含 List.

    (即,List.)

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

<小时>

这里的非技术总结是,规范暗示ListList之间既没有子类型也没有超类型关系>.

虽然从List转换为List>应该是安全,但这是不允许的.(这是安全的,因为两者都是一个 List,可以存储任何类型的 List,但是 List 强加了更多对其元素在检索后如何使用的限制.)

不幸的是,没有实际原因导致编译失败,除了原始类型很奇怪并且它们的使用有问题.

A raw list converts to List<?> just fine. Why can't a list of raw lists convert to a list of 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 (to mitigate the xy problem):

An API I'm using returns List<JAXBElement>. I happen to know that it is always List<JAXBElement<String>>. I plan to loop and build my own List<String>, but I was trying to fix (but not suppress) the raw type compiler warning when I write List<JAXBElement> raw = api();.

I tried:

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

but these give the type mismatch error.

Interestingly, this gives no warning or error:

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

解决方案

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

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

First let's sort out why these are actually unrelated assignments. That is, they're governed by different rules.

#1 is called an unchecked conversion:

There is an unchecked conversion from the raw class or interface type (§4.8) G to any parameterized type of the form G<T1,...,Tn>.

Specifically it is a special case of an assignment context just for this scenario:

If, after [other possible conversions] have been applied, the resulting type is a raw type, an unchecked conversion may then be applied.

#2 requires a reference type conversion; however the problem with it is that it is not a widening conversion (which is the kind of reference conversion that would be implicitly allowed without a cast).

Why is that? Well, this is specifically governed by the rules of generic subtyping and more specifically this bullet point:

Given a generic type declaration C<F1,...,Fn> (n > 0), the direct supertypes of the parameterized type C<T1,...,Tn>, where Ti (1 ≤ in) is a type, are all of the following:

  • C<S1,...,Sn>, where Si contains Ti (1 ≤ in).

This refers us to something the JLS calls containment, where to be a valid assignment, the arguments of the left-hand side must contain the arguments of the right-hand side. Containment largely governs generic subtyping since "concrete" generic types are invariant.

You may be familiar with the ideas that:

  • a List<Dog> is not a List<Animal>
  • but a List<Dog> is a List<? extends Animal>.

Well, the latter is true because ? extends Animal contains Dog.

So the question becomes "does a type argument List<?> contain a raw type argument List"? And the answer is no: although List<?> is a subtype of List, this relationship does not hold for type arguments.

There is no special rule that makes it true: List<List<?>> is not a subtype of List<List> for essentially the same reason List<Dog> is not a subtype of List<Animal>.

So because List<List> is not a subtype of List<List<?>>, the assignment is invalid. Similarly, you cannot perform a direct narrowing conversion cast because List<List> is not a supertype of List<List<?>> either.


To make the assignment you can still apply a cast. There are three ways to do it that seem reasonable to me.

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

(You can substitute JAXBElement for the inner List.)

Your use-case for this casting should be safe because List<List<?>> is a more restrictive type than List<List>.

  • The raw type statement is a widening cast then unchecked assignment. This works because, as shown above, any parameterized type can be converted to its raw type and vice-versa.

  • The slightly safer statement (named as such because it loses less type information) is a widening cast then narrowing cast. This works by casting to a common supertype:

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

    The bounded wildcard allows the type arguments to be considered for subtyping via containment.

    The fact that List<? extends List> is considered a supertype of List<List<?>> can be proven with transitivity:

    1. ? extends List contains ? extends List<?>, because List is a supertype of List<?>.

    2. ? extends List<?> contains List<?>.

    3. Therefore ? extends List contains List<?>.

    (That is, List<? extends List> :> List<? extends List<?>> :> List<List<?>>.)

  • The third example works in a way that's similar to the second example, by casting to a common supertype List<? super List<?>>. Since it doesn't use a raw type, we can suppress one less warning.


The non-technical summary here is that the specification implies that there is neither subtype nor supertype relationship between List<List> and List<List<?>>.

Although converting from List<List> to List<List<?>> should be safe, it is not allowed. (It's safe because both are a List that can store any kind of List, but a List<List<?>> imposes more restrictions on how its elements can be used after they are retrieved.)

There is unfortunately no practical reason this fails to compile except that raw types are strange and usage of them is problematic.

这篇关于无法从 List<List> 转换到列表&lt;列表&lt;?&gt;&gt;的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆