为什么在 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)
正在研究 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)