编译器如何区分 <? 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,这意味着不确定,但它可以是 IntegerLong 还有。所以我们需要转换为:

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

这将简单地打印每个列表中的元素。