繁体   English   中英

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

[英]Why does the Java compiler 11 use invokevirtual to call private methods?

当使用 OpenJDK 8 中的 Java 编译器编译下面的代码时,对foo()的调用是通过invokespecial完成的,但是当使用 OpenJDK 11 时,会发出一个invokevirtual

public class Invoke {
  public void call() {
    foo();
  }

  private void foo() {}
}

使用javac 1.8.0_282 时javap -v -p的 Output :

  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

使用javac 11.0.10 时javap -v -p的 Output :

  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 编译这段代码,我们可以在javapinvokevirtual中看到,在foo()getValueInNested()中都使用了 invokevirtual:

  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替换它的用例不等效?
  • Nested.getValueInNested()中对getValue()的调用如何不从Derived中选择方法,因为它是通过invokevirtual调用的?

这是作为https://openjdk.java.net/jeps/181的一部分完成的:基于嵌套的访问控制,因此 JVM 可以允许从嵌套类访问私有方法。

在此更改之前,编译器必须在嵌套 class 调用的Base class 中生成一个受包保护的合成方法。 该合成方法将依次调用Base class 中的私有方法。 Java 11 中的功能增强了 JVM 以允许编译器不必生成合成方法。

关于invokevirtual是否会调用Derived class 中的方法这一点,答案是否定的。 私有方法仍然不受运行时 class 的方法选择的影响(这从未改变过):

在执行invokeinterfaceinvokevirtual指令期间,根据(i)堆栈上object的运行时类型和(ii)先前由指令解析的方法来选择方法。 select 一个方法关于 class 或接口 C 和一个方法 m ZE1E1D3D405731283DEECEE6的规则如下:

  1. 如果 m R标记为ACC_PRIVATE ,则它是选定的方法。

编辑:

基于评论“如果私有方法是从方法所有者 class 调用的,并且如果从嵌套的 class 调用它,则使用 invokevirtual 是否仍然有效?”

正如 Holger 提到的,是的,它是有效的,但是基于JEP ,我猜为了简单起见,决定切换到invokevirtual (虽然我无法证实这一点,这只是一个猜测):

随着访问规则的改变,以及对字节码规则的适当调整,我们可以允许生成调用字节码的简化规则:

  • invokespecial 用于私有的 nestmate 构造函数,
  • invokevirtual 用于私有非接口,nestmate 实例方法,
  • 私有接口的invokeinterface,nestmate实例方法;
  • 私人巢友的invokestatic,static方法

来自JDK-8197445 的另一个有趣的注释:JEP 181 的实现:基于嵌套的访问控制

传统上, invokespecial用于调用private成员,尽管invokevirtual也具有此功能。 我们不需要在不同的 class 中调用private方法来使用invokevirtual ,而不是扰乱由invokespecial强制执行的有关超类型的复杂规则。

充值到一个已经很好的答案

认为在已经提供和接受的答案中添加更多信息是合适的,尽管这不是绝对必要的,但它可能有助于扩大理解,因此它符合 SO 用户的最大利益。

基于嵌套的访问控制

在早期版本中,在 Java 11 之前,正如@ma在接受的答案中已经指出的那样,编译器需要创建桥接方法以允许类在这种情况下访问彼此的私有成员。 这些可访问性扩大的桥接方法在执行上下文中被调用,编译器将代码插入到正在运行的程序中。

这样做会增加已部署应用程序的大小并增加复杂性,而且更难理解幕后发生的事情。

Java 11引入了基于嵌套的访问控制的概念。 除了嵌套对象的概念和JVM中的相关访问规则外,这还允许类和接口相互嵌套。

嵌套类型可以是私有字段、方法和构造函数。

使用更新的反射 API 您现在可以查询有关基于嵌套的访问控制功能的信息。

Java 11 中的一些新优点

getNestHost()方法用于获取巢主机的名称, isNestmateOf()方法可用于检查class是否为巢友。 此外, getNestMembers()方法返回一个嵌套成员数组。

这是一个通用示例的链接,由 Baeldung.com 提供,基于巢的访问控制使得好处非常突出恕我直言。

请注意,反汇编代码中没有编译器生成的桥接方法。 此外,在上面链接的示例中,内部 class 现在可以直接调用 outerPrivate() 方法。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM