为什么java编译器11使用invokevirtual来调用私有方法?

zaqlnxep  于 2021-07-12  发布在  Java
关注(0)|答案(1)|浏览(570)

当使用来自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 ?

a0x5cqrl

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 .

相关问题