当使用来自openjdk8的java编译器编译下面的代码时,调用 foo()
是通过 invokespecial
,但是当使用openjdk 11时 invokevirtual
发射。
public class Invoke {
public void call() {
foo();
}
private void foo() {}
}
的输出 javap -v -p
什么时候 javac
使用1.8.0282:
public void call();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #2 // Method foo:()V
4: return
的输出 javap -v -p
什么时候 javac
使用11.0.10:
public void call();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokevirtual #2 // Method foo:()V
4: return
我不明白为什么 invokevirtual
因为不能重写 foo()
.
经过一番挖掘,似乎 invokevirtual
在私有方法上允许嵌套类从外部类调用私有方法。所以我尝试了下面的代码:
public class Test{
public static void main(String[] args) {
// Build a Derived such that Derived.getValue()
// somewhat "exists".
System.out.println(new Derived().foo());
}
public static class Base {
public int foo() {
return getValue() + new Nested().getValueInNested();
}
private int getValue() {
return 24;
}
private class Nested {
public int getValueInNested() {
// This is getValue() from Base, but would
// invokevirtual call the version from Derived?
return getValue();
}
}
}
public static class Derived extends Base {
// Let's redefine getValue() to see if it is picked by the
// invokevirtual from getValueInNested().
private int getValue() {
return 100;
}
}
}
用11编译这段代码,我们可以在 javap
那个 invokevirtual
都用在 foo()
而且在 getValueInNested()
:
public int foo();
descriptor: ()I
flags: (0x0001) ACC_PUBLIC
Code:
stack=4, locals=1, args_size=1
0: aload_0
//**HERE**
1: invokevirtual #2 // Method getValue:()I
4: new #3 // class Test$Base$Nested
7: dup
8: aload_0
9: invokespecial #4 // Method Test$Base$Nested."<init>":(LTest$Base;)V
12: invokevirtual #5 // Method Test$Base$Nested.getValueInNested:()I
15: iadd
16: ireturn
public int getValueInNested();
descriptor: ()I
flags: (0x0001) ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: getfield #1 // Field this$0:LTest$Base;
//**HERE**
4: invokevirtual #3 // Method Test$Base.getValue:()I
7: ireturn
所有这些都有点令人困惑,并引发了一些问题:
为什么? invokevirtual
用于调用私有方法?是否有一个用 invokespecial
不是等价的吗?
你的电话怎么打 getValue()
在 Nested.getValueInNested()
不从中选择方法 Derived
因为它叫via invokevirtual
?
1条答案
按热度按时间a0x5cqrl1#
这是作为https://openjdk.java.net/jeps/181:基于嵌套的访问控制,以便jvm可以允许从嵌套类访问私有方法。
在此更改之前,编译器必须在
Base
类,嵌套类调用它。该合成方法将依次调用Base
班级。java11中的特性增强了jvm,使得编译器不必生成合成方法。关于是否
invokevirtual
将调用Derived
类,答案是否。私有方法仍然不受运行时类的方法选择的影响(这从未更改):在执行
invokeinterface
或者invokevirtual
指令时,根据(i)堆栈上对象的运行时类型和(ii)先前由该指令解析的方法来选择方法。对于类或接口c和方法mr,选择方法的规则如下:如果mr被标记
ACC_PRIVATE
,则它是选定的方法。编辑:
根据注解“如果私有方法是从方法所有者类调用的,那么仍然使用invokespecial是有效的吗?如果私有方法是从嵌套类调用的,那么使用invokevirtual是有效的吗?”
正如霍尔格提到的,是的,这是有效的,但根据正义与平等党,我猜是作出了一个决定,切换到
invokevirtual
为了简单起见(我无法证实这一点,这只是猜测):通过对访问规则的更改以及对字节码规则的适当调整,我们可以简化生成调用字节码的规则:
特别是对于私有的nestmate构造函数,
invokevirtual用于私有非接口、nestmate示例方法,
invokeinterface for private接口,nestmate示例方法;和
私有nestmate的invokestatic,静态方法
jdk-8197445的另一个有趣的注解:jep 181的实现:基于嵌套的访问控制:
传统上,
invokespecial
用于调用private
不过,成员们invokevirtual
也有这个能力。而不是扰乱invokespecial
,我们需要调用private
要使用的其他类中的方法invokevirtual
.