为什么在 JVM 中相对于超类的字段和方法解析不同?

Why are field and method resolution relative to superclasses different in the JVM?

正在研究 JVM 内部结构,对此示例有点困惑,该示例同时具有子class 中的覆盖方法和隐藏的实例变量:

class CA extends Object {
    public int ivar;
        
    public void two() {
        ivar = 20;
        this.three();
    }
    
    public void three() {
        System.out.println("three called in CA");
        System.out.println(ivar);
    }
    
}
class CB extends CA {
    public int ivar;
    
    public void one() {
        ivar = 10;
        two();
    }
    
    public void three() {
        System.out.println("three called in CB");
        System.out.println(ivar);
        super.three();
    }
    
    public static void main(String[] args) {
        CB cb = new CB();
        cb.one();
    }
}

这会产生以下输出:

three called in CB
10
three called in CA
20

方法调用完全符合预期,但我观察到实例变量的行为不同——当它在 superclass 的实现中设置和访问时,它正在访问“阴影”值。

我似乎无法在 JVM 规范的第 5.4.3.2 节和第 5.4.3.3 节(分别是字段和方法)中找到关于这些行为如何工作的讨论。两个部分的措辞非常相似:

If C declares a field with the name and descriptor specified by the field reference, field lookup succeeds. [...]

[...] if C declares a method with the name and descriptor specified by the method reference, method lookup succeeds.

这向我表明,上面示例中的 ivar 行为是合理的——CA 的 class 文件不知道 CB,因此它的字段 ref 指向它自己的 ivar 版本 并且 JVM 在执行 class 的方法时设置和获取值时尊重它。

但是覆盖方法似乎也是如此,我没有找到解析算法来谈论“当前对象 class”与“实现对象 class”等。我找错地方了吗?有人可以为我阐明 design/implementation 吗?谢谢!

这在 JLS 的 15.12 中记录,方法调用表达式:

Resolving a method name at compile time is more complicated than resolving a field name because of the possibility of method overloading. Invoking a method at run time is also more complicated than accessing a field because of the possibility of instance method overriding.

这只是一个注释,方法调用的实际规则非常冗长,您想要的部分是 15.12.4.4。找到要调用的方法。

If the method m of class or interface C is private, then it is the method to be invoked.

Otherwise, overriding may occur. A dynamic method lookup, specified below, is used to locate the method to invoke. The lookup procedure starts from class R, the actual run-time class of the target object.

https://docs.oracle.com/javase/specs/jls/se15/html/jls-15.html#jls-15.12

我没有看到你的困惑——它输出的正是我所期望的。

main calls
CB.one which sets CB.ivar to 10, then calls
CA.two which sets CA.ivar to 20, then calls
CB.three which prints CB.ivar (10), then calls
CA.three which prints CA.ivar (20)