Java 泛型中的 Get-put 原则

Get-put principles in Java generics

我最近在学习 Java 泛型并遇到了所谓的 "get-put" 原则,即哪种通配符允许您从集合中添加或删除某些类型的对象(参考,例如 https://flylib.com/books/en/4.79.1.18/1/).

我的问题是,据说您只能从使用 <? super SomeClass> 的集合中获取对象以外的任何东西。但是下面的代码是完全有效的:

List<? super A> list = new ArrayList<>();
list.add(new A());
System.out.println((list.get(0).toString()));

哪里

class A{
    @Override
    public String toString(){
        return "super.toString();";
    }
}

搞笑的是,居然用了重写的toString(),违背了原理。

此外,

 A a = list.get(0);

失败。

谁能解释一下这是什么问题?

本例中 list.get() 的引用类型是 Object,而 class 声明了 toString() 所以你可以调用它。

但是Java使用dynamic dispatch来选择执行哪个方法。由于对象的运行时类型是 A,这就是调用的版本。

您的代码等同于:

Object obj = new A();
System.out.println(obj.toString()); /* Prints A's version */
A a = obj; /* Fails to compile. */

如您所见,该行为与泛型无关。

这里没有发生意外。让我们 运行 完成以下步骤:

List<? super A> list = new ArrayList<>();

我们有 List?,在 class 层次结构中位于 A 之上。每个?都是一个Object,所以我们可以把它看成一个List<Object>。继续前进...

list.add(new A());
System.out.println((list.get(0)

到目前为止一切正常 - list 内部有一个 A,它被提取为 Object

                               .toString()));

我们调用 toString,它是在 ObjectA)上调用的。动态分派继续调用 AtoString 方法(类型层次结构中的最低定义)。然而,这是完全合法的,因为 toString 是为 ObjectA 定义的。继续...

 A a = list.get(0); //oops!

正如预期的那样,这中断了,因为我们尝试将 ObjectList<Object> 转换为 A,而不进行转换(例如 A a = (A) list.get(0);)。