Почему компилятор Java 11 использует invokevirtual для вызова приватных методов?
Когда я компилирую приведённый ниже код с помощью компилятора 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
Всё это довольно запутано и вызывает несколько вопросов:
- Почему
invokevirtual
используется для вызова приватных методов? Существует ли случай, когда замена его наinvokespecial
не была бы эквивалентна? - Как вызов
getValue()
вNested.getValueInNested()
не вызывает метод изDerived
, если он вызывается черезinvokevirtual
?
Java 8: Преобразование List<V> в Map<K, V>
Почему не стоит использовать Optional в аргументах в Java 8?
Регистрация нескольких хранилищ ключей в JVM
Вывод типа с помощью рефлексии для лямбд в Java 8
Java Лямбда-выражения [закрыто]