Как хранится результат 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 каждый раз).

Другие вопросы по тегам