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调用包含的内容,在哪里是有效的赋值,左侧的参数必须包含右侧的参数.遏制主要控制通用子类型,因为"具体"泛型类型是不变的.
您可能熟悉以下想法:
List<Dog>不是List<Animal>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<?>>可以通过传递性来证明:
? extends List包含? extends List<?>,因为List是超类型List<?>.
? extends List<?>包含List<?>.
因此? 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<?>>对它们的元素在检索后如何使用会施加更多限制.)
遗憾的是,除了原始类型很奇怪并且使用它们存在问题之外,没有任何实际原因无法编译.