Что такое invokedynamic и как мне его использовать?

159

Я продолжаю слышать обо всех новых интересных функциях, которые добавляются в JVM, и одна из этих интересных функций - динамическая. Я хотел бы знать, что это такое и как это делает рефлексивное программирование на Java легче или лучше?

Дэвид К.
источник

Ответы:

165

Это новая инструкция JVM, которая позволяет компилятору генерировать код, который вызывает методы с более слабой спецификацией, чем это было возможно ранее - если вы знаете, что такое « типизация утки », invokedynamic в основном допускает типизацию «утки». Вы не так уж много можете сделать, как программист на Java; если вы создаете инструмент, вы можете использовать его для создания более гибких и эффективных языков на основе JVM. Вот очень приятный пост в блоге, который дает много деталей.

Эрнест Фридман-Хилл
источник
3
В повседневном Java-программировании нередко можно увидеть отражение, используемое для динамического вызова методов meth.invoke(args). Итак, как это invokedynamicсочетается с meth.invoke?
Дэвид К.
1
Сообщение в блоге, о MethodHandleкотором я упоминаю, говорит о том , что это действительно то же самое, но с гораздо большей гибкостью. Но реальная сила во всем этом заключается не в дополнениях к языку Java, а в возможностях самой JVM в поддержке других языков, которые по своей природе более динамичны.
Эрнест Фридман-Хилл
1
Кажется, что Java 8 переводит некоторые из лямбд, использующих их, invokedynamicчто делает их производительными (по сравнению с обертыванием их в анонимный внутренний класс, который был почти единственным выбором перед введением invokedynamic). Скорее всего, многие функциональные языки программирования поверх JVM предпочтут компилировать это вместо anon-inner-classes.
Надер Ганбари
2
Просто небольшое предупреждение, что запись в блоге от 2008 года безнадежно устарела и не отражает фактическое состояние выпуска (2011).
Хольгер
9

Некоторое время назад в C # добавлена ​​классная функция, динамический синтаксис в C #

Object obj = ...; // no static type available 
dynamic duck = obj;
duck.quack(); // or any method. no compiler checking.

Думайте об этом как о синтаксическом сахаре для рефлексивных вызовов методов. У него могут быть очень интересные приложения. см. http://www.infoq.com/presentations/Statically-Dynamic-Typing-Neal-Gafter

Нил Гафтер, ответственный за динамический тип C #, только что перешел с SUN на MS. Так что не исключено, что в SUN обсуждались те же самые вещи.

Я помню вскоре после этого, какой-то Java чувак объявил что-то подобное

InvokeDynamic duck = obj;
duck.quack(); 

К сожалению, эта функция отсутствует в Java 7. Очень разочарован. Для программистов на Java у них нет простого способа воспользоваться преимуществами invokedynamicсвоих программ.

irreputable
источник
41
invokedynamicникогда не предназначался для программистов на Java. ИМО, это совсем не соответствует философии Java. Он был добавлен как функция JVM для не-Java языков.
Марк Питерс
5
@ Марк Никогда не задумывался кем? Это не значит, что у знаменитостей языка Java есть четкая структура власти или четко определенное коллективное «намерение». Что касается философии языка - это вполне осуществимо, см. Объяснение Нила Гафтера (предателя!): Infoq.com/presentations/Statically-Dynamic-Typing-Neal-Gafter
неопровержимо
3
@mark peters: invokedynamic на самом деле также предназначен для Java-программистов, только не доступных напрямую. Это основа для замыканий Java 8.
М Платвоет
2
@irreputable: никогда не предназначался авторами JSR. Это говорит о том, что JSR называется «Поддержка динамически типизированных языков на платформе Java». Java не является динамически типизированным языком.
Марк Питерс
5
@M Platvoet: я не был в курсе закрытия, но это, безусловно, не будет абсолютным требованием для закрытия. Другим вариантом, который они обсуждали, было создание синтаксического сахара замыканий для анонимных внутренних классов, что можно было сделать без изменения спецификации виртуальной машины. Но я хотел сказать, что JSR никогда не предназначался для динамической типизации языка Java, и это ясно, если вы читаете JSR.
Марк Питерс
4

Есть две концепции, которые нужно понять, прежде чем продолжать вызывать динамику.

1. Статическая и динамическая типизация

Static - проверка типов преформ во время компиляции (например, Java)

Динамический - проверка типов преформ во время выполнения (например, JavaScript)

Проверка типа - это процесс проверки того, что программа безопасна по типу, то есть проверка типизированной информации для переменных класса и экземпляра, параметров метода, возвращаемых значений и других переменных. Например, Java знает о int, String, .. во время компиляции, в то время как тип объекта в JavaScript может быть определен только во время выполнения

2. Сильный против слабого набора текста

Сильный - указывает ограничения на типы значений, предоставляемых его операциям (например, Java)

Слабый - преобразует (преобразует) аргументы операции, если эти аргументы имеют несовместимые типы (например, Visual Basic)

Зная, что Java является статически и слабо типизированным, как вы реализуете динамически и строго типизированные языки в JVM?

Invokedynamic реализует систему времени выполнения, которая может выбрать наиболее подходящую реализацию метода или функции - после компиляции программы.

Пример: имея (a + b) и ничего не зная о переменных a, b во время компиляции, активизировал динамическую привязку этой операции к наиболее подходящему методу в Java во время выполнения. Например, если выясняется, что a, b - строки, вызовите метод (String a, String b). Если выясняется, что a, b являются целыми числами, вызовите метод (int a, int b).

invokedynamic был представлен в Java 7.

Сабина Оразем
источник
4

Как часть моей статьи Java Records , я сформулировал мотивацию Inoke Dynamic. Давайте начнем с грубого определения Инди.

Представляем Indy

Invoke Dynamic (также известный как Indy ) был частью JSR 292, намереваясь улучшить поддержку JVM для языков динамического типа. После первого выпуска в Java 7 invokedynamicкод операции и его java.lang.invokeбагаж довольно широко используются динамическими языками на основе JVM, такими как JRuby.

Хотя indy специально разработан для улучшения поддержки динамического языка, он предлагает гораздо больше. Фактически, это подходит для использования там, где разработчик языка нуждается в любой форме динамичности, от акробатики динамического типа до динамических стратегий!

Например, лямбда-выражения Java 8 фактически реализованы с использованием языка invokedynamicJava, хотя это статически типизированный язык!

Определяемый пользователем байт-код

В течение некоторого времени JVM поддерживала четыре типа invokestaticвызова методов : вызывать статические методы, invokeinterfaceвызывать методы интерфейса, invokespecialвызывать конструкторы super()или частные методы и invokevirtualвызывать методы экземпляра.

Несмотря на различия, эти типы вызовов имеют одну общую черту: мы не можем обогатить их своей собственной логикой . Напротив, invokedynamic позволяет нам загружать процесс вызова любым способом, каким мы захотим. Затем JVM позаботится о непосредственном вызове метода Bootstrapped.

Как работает инди?

Когда JVM впервые видит invokedynamicинструкцию, она вызывает специальный статический метод Bootstrap Method . Метод начальной загрузки - это фрагмент кода Java, который мы написали для подготовки фактической логики, которая должна быть вызвана:

введите описание изображения здесь

Затем метод начальной загрузки возвращает экземпляр java.lang.invoke.CallSite. Это CallSiteдержит ссылку на фактический метод, то есть MethodHandle.

С этого момента каждый раз, когда JVM invokedynamicснова видит эту инструкцию, она пропускает медленный путь и напрямую вызывает основной исполняемый файл. JVM продолжает пропускать медленный путь, если что-то не меняется.

Пример: Java 14 Records

Java 14 Recordsпредоставляет хороший компактный синтаксис для объявления классов, которые должны быть глупыми держателями данных.

Учитывая эту простую запись:

public record Range(int min, int max) {}

Байт-код для этого примера будет выглядеть примерно так:

Compiled from "Range.java"
public java.lang.String toString();
    descriptor: ()Ljava/lang/String;
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokedynamic #18,  0 // InvokeDynamic #0:toString:(LRange;)Ljava/lang/String;
         6: areturn

В таблице методов начальной загрузки :

BootstrapMethods:
  0: #41 REF_invokeStatic java/lang/runtime/ObjectMethods.bootstrap:
     (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;
     Ljava/lang/invoke/TypeDescriptor;Ljava/lang/Class;
     Ljava/lang/String;[Ljava/lang/invoke/MethodHandle;)Ljava/lang/Object;
    Method arguments:
      #8 Range
      #48 min;max
      #50 REF_getField Range.min:I
      #51 REF_getField Range.max:I

Таким образом, вызывается метод начальной загрузки для Records, bootstrapкоторый находится в java.lang.runtime.ObjectMethodsклассе. Как видите, этот метод начальной загрузки ожидает следующие параметры:

  • Экземпляр MethodHandles.Lookupпредставления контекста поиска ( Ljava/lang/invoke/MethodHandles$Lookupчасть).
  • Имя методы (то есть toString, equals, hashCodeи т.д.) начальная загрузка будет ссылка. Например, когда значение равно toString, bootstrap вернет ConstantCallSite(a, CallSiteкоторый никогда не изменяется), которое указывает на фактическую toStringреализацию для этой конкретной Записи.
  • Для TypeDescriptorметода ( Ljava/lang/invoke/TypeDescriptor часть).
  • Маркер типа, т. Е. Class<?>Представляющий тип класса Record. Это Class<Range>в этом случае.
  • Пол-двоеточия списка всех компонентов имен, то есть min;max.
  • Один MethodHandleна компонент. Таким образом, метод начальной загрузки может создать на MethodHandleоснове компонентов для этой конкретной реализации метода.

invokedynamicИнструкция передает все эти аргументы метода начальной загрузки. Метод Bootstrap, в свою очередь, возвращает экземпляр ConstantCallSite. Это ConstantCallSiteсодержит ссылку на запрошенную реализацию метода, например toString.

Почему Инди?

В отличие от API-интерфейсов Reflection, API- java.lang.invokeинтерфейс достаточно эффективен, поскольку JVM может полностью видеть все вызовы. Поэтому JVM может применять все виды оптимизаций, пока мы максимально избегаем медленного пути!

В дополнение к аргументу эффективности, invokedynamicподход более надежен и менее хрупок из-за своей простоты .

Более того, сгенерированный байт-код для записей Java не зависит от количества свойств. Таким образом, меньше байт-кода и более быстрое время запуска.

Наконец, давайте предположим, что новая версия Java включает новую и более эффективную реализацию метода начальной загрузки. С invokedynamic, наше приложение может воспользоваться этим усовершенствованием без перекомпиляции. Таким образом, у нас есть какая-то прямая двоичная совместимость . Кроме того, это динамическая стратегия, о которой мы говорили!

Другие примеры

В дополнение к Java Records, динамический вызов был использован для реализации таких функций, как:

Али Дехгани
источник