Ответы:
Это новая инструкция JVM, которая позволяет компилятору генерировать код, который вызывает методы с более слабой спецификацией, чем это было возможно ранее - если вы знаете, что такое « типизация утки », invokedynamic в основном допускает типизацию «утки». Вы не так уж много можете сделать, как программист на Java; если вы создаете инструмент, вы можете использовать его для создания более гибких и эффективных языков на основе JVM. Вот очень приятный пост в блоге, который дает много деталей.
MethodHandleкотором я упоминаю, говорит о том , что это действительно то же самое, но с гораздо большей гибкостью. Но реальная сила во всем этом заключается не в дополнениях к языку Java, а в возможностях самой JVM в поддержке других языков, которые по своей природе более динамичны.
invokedynamicчто делает их производительными (по сравнению с обертыванием их в анонимный внутренний класс, который был почти единственным выбором перед введением invokedynamic). Скорее всего, многие функциональные языки программирования поверх JVM предпочтут компилировать это вместо anon-inner-classes.
Некоторое время назад в 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своих программ.
invokedynamicникогда не предназначался для программистов на Java. ИМО, это совсем не соответствует философии Java. Он был добавлен как функция JVM для не-Java языков.
Есть две концепции, которые нужно понять, прежде чем продолжать вызывать динамику.
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.
Как часть моей статьи Java Records , я сформулировал мотивацию Inoke Dynamic. Давайте начнем с грубого определения Инди.
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предоставляет хороший компактный синтаксис для объявления классов, которые должны быть глупыми держателями данных.
Учитывая эту простую запись:
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, динамический вызов был использован для реализации таких функций, как:
LambdaMetafactoryStringConcatFactory
meth.invoke(args). Итак, как этоinvokedynamicсочетается сmeth.invoke?