Как сохраняется результат 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 всегда является экземпляр целевого типа, который является типом переменной, которую мы назначаем со ссылкой на метод?


person Miguel Gamboa    schedule 24.03.2015    source источник


Ответы (2)


Каждый invokedynamic байт-код ссылается на соответствующий CONSTANT_InvokeDynamic_info в пуле констант. Эта структура содержит дескриптор метода, который используется для получения типов аргументов и типа возвращаемого значения для этой инструкции invokedynamic.

В вашем примере дескриптор метода ()LI; вычисляется во время перевода исходного кода в байт-код.

8: invokedynamic #4,  0              // InvokeDynamic #0:foo:()LI;
                                                             ^^^^^

Это означает, что этот конкретный байт-код не ожидает аргументов и всегда выдает результат типа I.

person apangin    schedule 24.03.2015

Результат инструкции invokedynamic в том виде, в котором он используется в лямбда-выражениях и ссылках на методы Java 8, действительно является экземпляром целевого функционала interface.

Это не результат инструкции invokedynamic, который запоминается JVM, а CallSite, возвращаемый методом начальной загрузки, в случае, если новая Java 8 включает один из двух методов LambdaMetafactory.

Экземпляры CallSite, связанные с инструкцией invokedynamic, инкапсулируют поведение, а не конкретное значение результата. Фактическое поведение, обеспечиваемое LambdaMetafactory, намеренно не определено, чтобы обеспечить большую степень свободы, но текущая реализация демонстрирует два разных поведения.

Для незахватывающих лямбда-выражений поведение будет заключаться в возврате одного экземпляра, созданного во время начальной загрузки invokedynamic. Это можно сделать, создав файл обертка констант MethodHandle, завернутая в ConstantCallSite. В этом случае последующие выполнения инструкции invokedynamic будут оценивать этот экземпляр.

Для лямбда-выражений, которые захватывают значения, инструкция будет связана с конструктором или фабричным методом сгенерированного класса, который принимает захваченные значения. Таким образом, последующие выполнения инструкции invokedynamic будут вести себя как обычная конструкция объекта (которая каждый раз создает новый экземпляр класса, который реализует цель interface).

person Holger    schedule 24.03.2015