编译器如何区分 <? extends Number> from <Number> 当它们在类型擦除后看起来都被编译成相同的东西时?
How does the compiler differentiate <? extends Number> from <Number> when they both seemingly get compiled to the same thing after type erasure?
我正在努力理解 java generic covariancy,并且我理解为什么
List<Number> list = new ArrayList<Integer>();
Integer = list.get(0);
是不允许的,因此不会将浮点数放入列表中并在以后引起问题。我相信类型擦除会将其编译为:
List list = new ArrayList();
Integer = (Integer) list.get(0);
这是允许的:
List<? extends Number> list = new ArrayList<Integer>();
Integer = list.get(0);
在类型擦除之后这不会编译成同样的东西吗?
通配符如何帮助编译器强制不将包含整数的浮点数放入列表中?
如果您有 List<? extends Number> list
,您就无法将 任何内容 放入结果列表中。您的列表是 "list containing some objects which extend Number
",因此使用任何参数调用 add()
与此类型不兼容。所以这就是编译器控制你是否不放置浮点数的方式:它不允许放置任何东西。
通常只在您不再打算修改列表而只想阅读的地方将类型更改为 List<? extends Number>
。
Tagir 回答得很好,我想补充几点。
首先,以下甚至不会编译:
List<? extends Number> list = new List<Integer>();
我希望你知道 List
是一个接口。所以现在我们有:
List<? extends Number> list = new ArrayList<Integer>();
这里list
是一个List<? extends Number>
类型的引用,它只是意味着它可以引用一个列表,其中每个元素是Number
或者更具体地说,每个元素都扩展 class Number
所以这意味着以下所有内容都是允许的:
list = new ArrayList<Float>();
list = new ArrayList<Long>();
但我们不能实例化为 Object
的列表,因为 Object
不是 Number
。
list = new ArrayList<Object>(); // compile time error
现在,当我们从这个列表中取出元素时,我们只知道一件事可以肯定它将成为 Number
,这意味着不确定,但它可以是 Integer
或 Long
还有。所以我们需要转换为:
Integer integer = (Integer) list.get(0);
Float floatValue = (Float) list.get(0);
Number number = list.get(0); // no casting if assigning it to Number
这主要在我们生产元素而不是消耗它们时有用(检查 PECS)。例如在打印元素时很有用,但当你想在其中添加任何元素时就没用了,因为它甚至不会编译。
private static void printMe(List<? extends Number> numbers) {
for(Number number : numbers) {
System.out.println(number);
}
}
这可以调用为:
List<Integer> integerList = new ArrayList<>();
integerList.add(10);integerList.add(20);integerList.add(30);
printMe(integerList);
List<Long> longList = new ArrayList<>();
longList.add((long) 4.5);longList.add((long) 6.5);longList.add((long) 7.5);
printMe(longList);
这将简单地打印每个列表中的元素。
我正在努力理解 java generic covariancy,并且我理解为什么
List<Number> list = new ArrayList<Integer>();
Integer = list.get(0);
是不允许的,因此不会将浮点数放入列表中并在以后引起问题。我相信类型擦除会将其编译为:
List list = new ArrayList();
Integer = (Integer) list.get(0);
这是允许的:
List<? extends Number> list = new ArrayList<Integer>();
Integer = list.get(0);
在类型擦除之后这不会编译成同样的东西吗? 通配符如何帮助编译器强制不将包含整数的浮点数放入列表中?
如果您有 List<? extends Number> list
,您就无法将 任何内容 放入结果列表中。您的列表是 "list containing some objects which extend Number
",因此使用任何参数调用 add()
与此类型不兼容。所以这就是编译器控制你是否不放置浮点数的方式:它不允许放置任何东西。
通常只在您不再打算修改列表而只想阅读的地方将类型更改为 List<? extends Number>
。
Tagir 回答得很好,我想补充几点。 首先,以下甚至不会编译:
List<? extends Number> list = new List<Integer>();
我希望你知道 List
是一个接口。所以现在我们有:
List<? extends Number> list = new ArrayList<Integer>();
这里list
是一个List<? extends Number>
类型的引用,它只是意味着它可以引用一个列表,其中每个元素是Number
或者更具体地说,每个元素都扩展 class Number
所以这意味着以下所有内容都是允许的:
list = new ArrayList<Float>();
list = new ArrayList<Long>();
但我们不能实例化为 Object
的列表,因为 Object
不是 Number
。
list = new ArrayList<Object>(); // compile time error
现在,当我们从这个列表中取出元素时,我们只知道一件事可以肯定它将成为 Number
,这意味着不确定,但它可以是 Integer
或 Long
还有。所以我们需要转换为:
Integer integer = (Integer) list.get(0);
Float floatValue = (Float) list.get(0);
Number number = list.get(0); // no casting if assigning it to Number
这主要在我们生产元素而不是消耗它们时有用(检查 PECS)。例如在打印元素时很有用,但当你想在其中添加任何元素时就没用了,因为它甚至不会编译。
private static void printMe(List<? extends Number> numbers) {
for(Number number : numbers) {
System.out.println(number);
}
}
这可以调用为:
List<Integer> integerList = new ArrayList<>();
integerList.add(10);integerList.add(20);integerList.add(30);
printMe(integerList);
List<Long> longList = new ArrayList<>();
longList.add((long) 4.5);longList.add((long) 6.5);longList.add((long) 7.5);
printMe(longList);
这将简单地打印每个列表中的元素。