Ответы:
Это новая инструкция 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 фактически реализованы с использованием языка invokedynamic
Java, хотя это статически типизированный язык!
В течение некоторого времени 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, динамический вызов был использован для реализации таких функций, как:
LambdaMetafactory
StringConcatFactory
meth.invoke(args)
. Итак, как этоinvokedynamic
сочетается сmeth.invoke
?