0

Почему компилятор Java 11 использует invokevirtual для вызова приватных методов?

3

Когда я компилирую приведённый ниже код с помощью компилятора Java из OpenJDK 8, вызов метода foo() осуществляется с помощью invokespecial, но при использовании OpenJDK 11 генерируется invokevirtual.

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

  private void foo() {}
}

Вывод команды javap -v -p при использовании javac 1.8.0_282 выглядит так:

  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:

  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) {
    // Создаем Derived, чтобы Derived.getValue()
    // чуть-чуть "существовал".
    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() {
        // Это getValue() из Base, но вызовет ли 
        // invokevirtual версию из Derived?
        return getValue();
      }
    }
  }

  public static class Derived extends Base {

    // Переопределим getValue(), чтобы проверить, будет ли он 
    // использован вызовом invokevirtual из getValueInNested().
    private int getValue() {
      return 100;
    }
  }
}

При компиляции этого кода с помощью Java 11 мы видим, что invokevirtual используется как в foo(), так и в getValueInNested():

  public int foo();
    descriptor: ()I
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=4, locals=1, args_size=1
         0: aload_0
         // ** ЗДЕСЬ **
         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;
         // ** ЗДЕСЬ **
         4: invokevirtual #3  // Method Test$Base.getValue:()I
         7: ireturn

Всё это довольно запутано и вызывает несколько вопросов:

  1. Почему invokevirtual используется для вызова приватных методов? Существует ли случай, когда замена его на invokespecial не была бы эквивалентна?
  2. Как вызов getValue() в Nested.getValueInNested() не вызывает метод из Derived, если он вызывается через invokevirtual?

0 ответ(ов)

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь