Как хранится результат invokedynamic?
В Java 8 появилась поддержка функций первого класса, которые позволяют назначать функции переменным. В этом случае переменные должны иметь тип функции, который определяется функциональным интерфейсом (интерфейсом только с одним абстрактным методом).
Итак, рассмотрим пример интерфейса I
и класс A
со следующим определением:
interface I{ int foo(); }
class A implements I{
public int foo(){return 7;}
public static int bar(){return 11;}
}
Мы можем назначить переменную типа I
экземпляр A
или ссылка на метод bar
из A
, Оба можно хранить на переменных типа I
, такие как:
I i1 = new A();
I i2 = A::bar;
Если мы проанализируем байт-коды, полученные в результате компиляции предыдущего кода, мы получим:
0: new #2 // class A
3: dup
4: invokespecial #3 // Method A."<init>":()V
7: astore_1
8: invokedynamic #4, 0 // InvokeDynamic #0:foo:()LI;
13: astore_2
За i1 = new A();
ясно, что соответствующая инструкция 7: astore_1
хранит экземпляр A
это совместимо с I
, Но в результате i2 = A::bar
мы храним результат 8: invokedynamic #4, 0
,
Итак, это означает, что результат invokedynamic
всегда является экземпляром целевого типа, который является типом переменной, которую мы назначаем с помощью ссылки на метод?
2 ответа
Каждый invokedynamic
Байт-код ссылается на соответствующую структуру CONSTANT_InvokeDynamic_info в пуле констант. Эта структура содержит дескриптор метода, который используется для получения типов аргументов и типа возвращаемого значения для этого invokedynamic
инструкция.
В вашем примере дескриптор метода ()LI;
вычисляется во время преобразования исходного кода в байт-код.
8: invokedynamic #4, 0 // InvokeDynamic #0:foo:()LI;
^^^^^
Это означает, что этот конкретный байт-код не ожидает аргументов и всегда выдает результат типа I
,
Результат invokedynamic
инструкция, то, как ее используют лямбда-выражения Java 8 и ссылки на методы, действительно является экземпляром целевого функционала interface
,
Это не результат invokedynamic
инструкция, которая запоминается JVM, но CallSite
который возвращается методом начальной загрузки, в случае новой версии Java 8 имеется один из двух методов LambdaMetafactory
,
CallSite
случаи, связанные с invokedynamic
инструкция инкапсулирует поведение, а не конкретное значение результата. Фактическое поведение, обеспеченное LambdaMetafactory
намеренно не определено, чтобы обеспечить широкую степень свободы, но текущая реализация демонстрирует два разных поведения.
Для лямбда-выражений без захвата поведение будет возвращать один экземпляр, который был создан во время invokedynamic
самонастройки. Это можно сделать, создав постоянную упаковку MethodHandle
завернутый в ConstantCallSite
, В этом случае последующие казни invokedynamic
Инструкция оценит к этому экземпляру.
Для лямбда-выражений, которые захватывают значения, инструкция будет связана с методом конструктора или фабрики сгенерированного класса, который принимает захваченные значения. Итак, последующие казни invokedynamic
инструкция будет вести себя как обычная конструкция объекта (которая создает новый экземпляр класса, который реализует цель interface
каждый раз).