为什么这个通用赋值是非法的?
Why is this generic assignment illegal?
我有一个class:
class Generic<T> {
List<List<T>> getList() {
return null;
}
}
当我用通配符声明 Generic
并调用 getList
方法时,以下赋值是非法的。
Generic<? extends Number> tt = null;
List<List<? extends Number>> list = tt.getList(); // this line gives compile error
这对我来说似乎很奇怪,因为根据 Generic
的声明,在调用 getList
时创建一个 Generic<T>
并得到一个 List<List<T>>
是很自然的。
事实上,它要求我这样写作业:
List<? extends List<? extends Number>> list = tt.getList(); // this one is correct
我想知道为什么第一个是非法的,为什么第二个是合法的。
我给出的例子只是一些示例代码来说明问题,你不必关心它们的含义。
错误信息:
Incompatable types:
required : List<java.util.List<? extends java.lang.Number>>
found: List<java.util.List<capture<? extends java.lang.Number>>>
关于您 运行 的通配符类型,这是一件棘手但有趣的事情!这很棘手,但当你理解它时确实合乎逻辑。
错误与以下事实有关:通配符 ? extends Number
不是指一个单一的具体类型,而是指某个未知类型。因此两次出现的 ? extend Number
不一定指的是同一类型,因此编译器不允许赋值。
详细解释
赋值中的right-hand-side,tt.getList()
,没有得到类型List<List<? extends Number>>
。相反,它的每次使用都由编译器分配一个唯一生成的捕获类型,例如称为 List<List<capture#1 extends Number>>
.
捕获类型List<capture#1 extends Number>
是List<? extends Number>
的子类型,但不是同一类型! (这是为了避免将不同的未知类型混合在一起。)
赋值中left-hand-side的类型是List<List<? extends Number>>
。此类型不允许 List<? extends Number>
的子类型作为外部列表的元素类型,因此 getList
的 return 类型不能用作元素类型。
另一方面,类型List<? extends List<? extends Number>>
确实允许List<? extends Number>
的子类型作为外部列表的元素类型。所以这是解决问题的正确方法。
动机
下面的示例代码演示了为什么赋值是非法的。通过一系列步骤,我们最终得到一个 List<Integer>
,它实际上包含 Float
s!
class Generic<T> {
private List<List<T>> list = new ArrayList<>();
public List<List<T>> getList() {
return list;
}
}
// Start with a concrete type, which will get corrupted later on
Generic<Integer> genInt = new Generic<>();
// Add a List<Integer> to genInt.list. This is not necessary for the
// main example but migh make things a little clearer.
List<Integer> ints = List.of(1);
genInt.getList().add(ints);
// Assign to a wildcard type as in the question
Generic<? extends Number> genWild = genInt;
// The illegal assignment. This doesn't compile normally, but we force it
// using an unchecked cast to see what would happen IF it did compile.
List<List<? extends Number>> list =
(List<List<? extends Number>>) (Object) genWild.getList();
// This is the crucial step:
// It is legal to add a List<Float> to List<List<? extends Number>>.
// list refers to genInt.list, which has type List<List<Integer>>.
// Heap pollution occurs!
List<Float> floats = List.of(1.0f);
list.add(floats);
// notInts in reality is the same list as floats!
List<Integer> notInts = genInt.getList().get(1);
// This statement reads a Float from a List<Integer>. A ClassCastException
// is thrown. The compiler must not allow us to end up here without any
// previous type errors or unchecked cast warnings.
Integer i = notInts.get(0);
您发现的修复方法是对 list
使用以下类型:
List<? extends List<? extends Number>> list = tt.getList();
这个新类型将类型错误从 list
的赋值转移到对 list.add(...)
.
的调用
以上说明了通配符类型的全部要点:跟踪可以安全读取和写入值的位置,而不会混淆类型和出现意外 ClassCastException
s。
一般经验法则
对于这种情况,当您使用通配符嵌套类型参数时,有一个一般的经验法则:
If the inner types have wildcards in them, then the outer types often need wildcards also.
否则内部通配符不能像你看到的那样“生效”。
参考资料
Java Tutorial 包含一些关于捕获类型的信息。
这个问题的答案包含通配符的一般信息:
What is PECS (Producer Extends Consumer Super)?
我有一个class:
class Generic<T> {
List<List<T>> getList() {
return null;
}
}
当我用通配符声明 Generic
并调用 getList
方法时,以下赋值是非法的。
Generic<? extends Number> tt = null;
List<List<? extends Number>> list = tt.getList(); // this line gives compile error
这对我来说似乎很奇怪,因为根据 Generic
的声明,在调用 getList
时创建一个 Generic<T>
并得到一个 List<List<T>>
是很自然的。
事实上,它要求我这样写作业:
List<? extends List<? extends Number>> list = tt.getList(); // this one is correct
我想知道为什么第一个是非法的,为什么第二个是合法的。
我给出的例子只是一些示例代码来说明问题,你不必关心它们的含义。
错误信息:
Incompatable types:
required :List<java.util.List<? extends java.lang.Number>>
found:List<java.util.List<capture<? extends java.lang.Number>>>
关于您 运行 的通配符类型,这是一件棘手但有趣的事情!这很棘手,但当你理解它时确实合乎逻辑。
错误与以下事实有关:通配符 ? extends Number
不是指一个单一的具体类型,而是指某个未知类型。因此两次出现的 ? extend Number
不一定指的是同一类型,因此编译器不允许赋值。
详细解释
赋值中的right-hand-side,
tt.getList()
,没有得到类型List<List<? extends Number>>
。相反,它的每次使用都由编译器分配一个唯一生成的捕获类型,例如称为List<List<capture#1 extends Number>>
.捕获类型
List<capture#1 extends Number>
是List<? extends Number>
的子类型,但不是同一类型! (这是为了避免将不同的未知类型混合在一起。)赋值中left-hand-side的类型是
List<List<? extends Number>>
。此类型不允许List<? extends Number>
的子类型作为外部列表的元素类型,因此getList
的 return 类型不能用作元素类型。另一方面,类型
List<? extends List<? extends Number>>
确实允许List<? extends Number>
的子类型作为外部列表的元素类型。所以这是解决问题的正确方法。
动机
下面的示例代码演示了为什么赋值是非法的。通过一系列步骤,我们最终得到一个 List<Integer>
,它实际上包含 Float
s!
class Generic<T> {
private List<List<T>> list = new ArrayList<>();
public List<List<T>> getList() {
return list;
}
}
// Start with a concrete type, which will get corrupted later on
Generic<Integer> genInt = new Generic<>();
// Add a List<Integer> to genInt.list. This is not necessary for the
// main example but migh make things a little clearer.
List<Integer> ints = List.of(1);
genInt.getList().add(ints);
// Assign to a wildcard type as in the question
Generic<? extends Number> genWild = genInt;
// The illegal assignment. This doesn't compile normally, but we force it
// using an unchecked cast to see what would happen IF it did compile.
List<List<? extends Number>> list =
(List<List<? extends Number>>) (Object) genWild.getList();
// This is the crucial step:
// It is legal to add a List<Float> to List<List<? extends Number>>.
// list refers to genInt.list, which has type List<List<Integer>>.
// Heap pollution occurs!
List<Float> floats = List.of(1.0f);
list.add(floats);
// notInts in reality is the same list as floats!
List<Integer> notInts = genInt.getList().get(1);
// This statement reads a Float from a List<Integer>. A ClassCastException
// is thrown. The compiler must not allow us to end up here without any
// previous type errors or unchecked cast warnings.
Integer i = notInts.get(0);
您发现的修复方法是对 list
使用以下类型:
List<? extends List<? extends Number>> list = tt.getList();
这个新类型将类型错误从 list
的赋值转移到对 list.add(...)
.
以上说明了通配符类型的全部要点:跟踪可以安全读取和写入值的位置,而不会混淆类型和出现意外 ClassCastException
s。
一般经验法则
对于这种情况,当您使用通配符嵌套类型参数时,有一个一般的经验法则:
If the inner types have wildcards in them, then the outer types often need wildcards also.
否则内部通配符不能像你看到的那样“生效”。
参考资料
Java Tutorial 包含一些关于捕获类型的信息。
这个问题的答案包含通配符的一般信息:
What is PECS (Producer Extends Consumer Super)?