在 ArrayList<List<?>> 通用调用中键入擦除
Type Erasure in ArrayList<List<?>> generic call
有人可以解释为什么以下代码无法编译:
ArrayList<List<?>> arrayList = new ArrayList<List<String>>();
为什么上面的代码无效,而这个运行没问题:
ArrayList<?> items = new ArrayList<List<String>>();
让我们从简单的列表开始。整数是数字的子class,因此您可以将整数分配给数字:
Integer i = 42;
Number n = i; // Ok
现在,让我们有一个 List<Number>
。我们不应该在那里放一个 List<Integer>
吗?
List<Number> numbers = new ArrayList<Integer>(); // Fails
不,它失败了。为什么?因为 Java 中的泛型是不变的,这意味着对于任何两个不同的类型 T1 和 T2,List<T1>
都不是 List<T2>
的子类型,List<T2>
也不是 [=22= 的子类型]
无论 T1 和 T2 之间的关系如何。好了,这正式解释了为什么上面的赋值是无效的,但是这背后的逻辑是什么?
List<Number>
应该可以容纳任何数字,这意味着如果我们有一个声明
List<Number> numbers;
那么我们应该可以做到 number.add(Integer(42))
、number.add(Double(Math.PI))
和任何其他 number.add(subClassOfNumber)
。但是,如果上面的赋值
List<Number> numbers = new ArrayList<Integer>(); // ?
将有效,然后 numbers
现在将保存列表,该列表只能存储整数,因此 numbers.add(anyNumberButInteger)
将失败,这违反了 List<Number>
的约定。
可以创建一个列表,其中包含 Number:
的某些子 class 的实例
List<? extends Number> list = Arrays.asList(5, 6, 7, 8); // Ok
您可以毫无问题地阅读此列表:
System.out.println(list.get(2)); // 7
但是除了 null 之外,你不能把任何东西放在那里,因为在编译时不可能知道列表元素的确切类型,因此不可能进行安全检查:
List<? extends Number> list = ThreadLocalRandom.current().nextBoolean() ?
new ArrayList<Integer>() :
new ArrayList<Double>();
list.add(null); // Ok
list.add(Double.valueOf(3.1415)); // Fails, because we don't know if Double(3.1415) is actually compatible with elements of the list
好了,现在来看你的例子:
List<List<?>> listOfLists = new ArrayList<List<String>>();
为了让这个赋值工作,你实际上需要在左边有一个 List<? extends List<String>>
,但是由于 Java 泛型是不变的,扩展 List<String>
的唯一类型是 List<String>
本身,所以唯一有效的组合是
List<List<String>> listOfListsOfStrings = new ArrayList<List<String>>();
可以创建一个表示列表的变量,该列表由一些使用原始类型的列表组成:
List<? extends List> listOfLists = new ArrayList<List<String>>(); // Compiles, but loses generics. DON'T DO THIS!
List<String> listOfStrings = listOfLists.get(0); // Compiles, even would work as expected assuming we populated listOfLists correctly
List<Integer> listOfIntegers = listOfLists.get(0); // Compiles, but does not work as expected (fail at runtime when accessing elements)
TLDR: 唯一的通用 class 即 super/extends List<String>
是 List<String>
,所以 List<some class which extends List<String>>
自动表示 List<List<String>>
.
有人可以解释为什么以下代码无法编译:
ArrayList<List<?>> arrayList = new ArrayList<List<String>>();
为什么上面的代码无效,而这个运行没问题:
ArrayList<?> items = new ArrayList<List<String>>();
让我们从简单的列表开始。整数是数字的子class,因此您可以将整数分配给数字:
Integer i = 42;
Number n = i; // Ok
现在,让我们有一个 List<Number>
。我们不应该在那里放一个 List<Integer>
吗?
List<Number> numbers = new ArrayList<Integer>(); // Fails
不,它失败了。为什么?因为 Java 中的泛型是不变的,这意味着对于任何两个不同的类型 T1 和 T2,List<T1>
都不是 List<T2>
的子类型,List<T2>
也不是 [=22= 的子类型]
无论 T1 和 T2 之间的关系如何。好了,这正式解释了为什么上面的赋值是无效的,但是这背后的逻辑是什么?
List<Number>
应该可以容纳任何数字,这意味着如果我们有一个声明
List<Number> numbers;
那么我们应该可以做到 number.add(Integer(42))
、number.add(Double(Math.PI))
和任何其他 number.add(subClassOfNumber)
。但是,如果上面的赋值
List<Number> numbers = new ArrayList<Integer>(); // ?
将有效,然后 numbers
现在将保存列表,该列表只能存储整数,因此 numbers.add(anyNumberButInteger)
将失败,这违反了 List<Number>
的约定。
可以创建一个列表,其中包含 Number:
的某些子 class 的实例List<? extends Number> list = Arrays.asList(5, 6, 7, 8); // Ok
您可以毫无问题地阅读此列表:
System.out.println(list.get(2)); // 7
但是除了 null 之外,你不能把任何东西放在那里,因为在编译时不可能知道列表元素的确切类型,因此不可能进行安全检查:
List<? extends Number> list = ThreadLocalRandom.current().nextBoolean() ?
new ArrayList<Integer>() :
new ArrayList<Double>();
list.add(null); // Ok
list.add(Double.valueOf(3.1415)); // Fails, because we don't know if Double(3.1415) is actually compatible with elements of the list
好了,现在来看你的例子:
List<List<?>> listOfLists = new ArrayList<List<String>>();
为了让这个赋值工作,你实际上需要在左边有一个 List<? extends List<String>>
,但是由于 Java 泛型是不变的,扩展 List<String>
的唯一类型是 List<String>
本身,所以唯一有效的组合是
List<List<String>> listOfListsOfStrings = new ArrayList<List<String>>();
可以创建一个表示列表的变量,该列表由一些使用原始类型的列表组成:
List<? extends List> listOfLists = new ArrayList<List<String>>(); // Compiles, but loses generics. DON'T DO THIS!
List<String> listOfStrings = listOfLists.get(0); // Compiles, even would work as expected assuming we populated listOfLists correctly
List<Integer> listOfIntegers = listOfLists.get(0); // Compiles, but does not work as expected (fail at runtime when accessing elements)
TLDR: 唯一的通用 class 即 super/extends List<String>
是 List<String>
,所以 List<some class which extends List<String>>
自动表示 List<List<String>>
.