Antworten:
Es ist eine neue JVM - Instruktion , die ein Compiler Code generieren können , welche Methoden mit einer lockereren Spezifikation nennt als dies bisher möglich war - wenn Sie wissen , was „ Duck Typing “ ist, invokedynamic erlaubt grundsätzlich für Ente eingeben. Sie als Java-Programmierer können nicht zu viel damit anfangen. Wenn Sie jedoch ein Tool-Ersteller sind, können Sie damit flexiblere und effizientere JVM-basierte Sprachen erstellen. Hier ist ein wirklich süßer Blog-Beitrag, der viele Details enthält.
MethodHandle
, was wirklich das Gleiche ist, aber mit viel mehr Flexibilität. Die wahre Kraft in all dem liegt jedoch nicht in der Ergänzung der Java-Sprache, sondern in den Fähigkeiten der JVM selbst, andere Sprachen zu unterstützen, die an sich dynamischer sind.
invokedynamic
, wodurch es performant wird (im Vergleich dazu, sie in eine anonyme innere Klasse zu packen, die vor der Einführung fast die einzige Wahl war invokedynamic
). Höchstwahrscheinlich werden sich viele funktionale Programmiersprachen zusätzlich zu JVM dafür entscheiden, diese anstelle von anon-inneren Klassen zu kompilieren.
Vor einiger Zeit hat C # eine coole Funktion hinzugefügt, die dynamische Syntax in C #
Object obj = ...; // no static type available
dynamic duck = obj;
duck.quack(); // or any method. no compiler checking.
Stellen Sie sich das als Syntaxzucker für reflektierende Methodenaufrufe vor. Es kann sehr interessante Anwendungen haben. Siehe http://www.infoq.com/presentations/Static-Dynamic-Typing-Neal-Gafter
Neal Gafter, der für den dynamischen Typ von C # verantwortlich ist, ist gerade von SUN zu MS übergegangen. Es ist also nicht unangemessen zu glauben, dass die gleichen Dinge in SUN besprochen wurden.
Ich erinnere mich, dass bald darauf ein Java-Typ etwas Ähnliches angekündigt hatte
InvokeDynamic duck = obj;
duck.quack();
Leider ist die Funktion in Java 7 nicht zu finden. Sehr enttäuscht. Für Java-Programmierer gibt es keine einfache Möglichkeit, die Vorteile invokedynamic
ihrer Programme zu nutzen.
invokedynamic
war nie für Java-Programmierer gedacht . IMO passt es überhaupt nicht zur Java-Philosophie. Es wurde als JVM-Funktion für Nicht-Java-Sprachen hinzugefügt.
Es sind zwei Konzepte zu verstehen, bevor Sie mit der Aufrufdynamik fortfahren.
1. Statische vs. Dynamin-Typisierung
Statisch - Vorformlings-Typprüfung zur Kompilierungszeit (z. B. Java)
Dynamisch - Preforms-Typprüfung zur Laufzeit (z. B. JavaScript)
Bei der Typprüfung wird überprüft, ob ein Programm typsicher ist. Dabei werden typisierte Informationen auf Klassen- und Instanzvariablen, Methodenparameter, Rückgabewerte und andere Variablen überprüft. Zum Beispiel kennt Java int, String, .. zur Kompilierungszeit, während der Typ eines Objekts in JavaScript nur zur Laufzeit bestimmt werden kann
2. Stark gegen schwach tippen
Stark - Gibt Einschränkungen für die Arten von Werten an, die für seine Operationen bereitgestellt werden (z. B. Java).
Schwach - konvertiert (Casts) Argumente einer Operation, wenn diese Argumente inkompatible Typen haben (z. B. Visual Basic)
Wie können Sie dynamisch und stark typisierte Sprachen in der JVM implementieren, wenn Sie wissen, dass Java statisch und schwach typisiert ist?
Die aufgerufene Dynamik implementiert ein Laufzeitsystem, das die am besten geeignete Implementierung einer Methode oder Funktion auswählen kann - nachdem das Programm kompiliert wurde.
Beispiel: Mit (a + b) und ohne Kenntnis der Variablen a, b zur Kompilierungszeit ordnet invokedynamic diese Operation zur Laufzeit der am besten geeigneten Methode in Java zu. Wenn sich beispielsweise herausstellt, dass a, b Strings sind, rufen Sie die Methode (String a, String b) auf. Wenn sich herausstellt, dass a, b Ints sind, rufen Sie die Methode (int a, int b) auf.
invokedynamic wurde mit Java 7 eingeführt.
Im Rahmen meiner Java Records Artikels habe ich über die Motivation hinter Inoke Dynamic gesprochen. Beginnen wir mit einer groben Definition von Indy.
Invoke Dynamic (auch als Indy bekannt ) war Teil von JSR 292 , um die JVM-Unterstützung für dynamische Typensprachen zu verbessern. Nach seiner ersten Veröffentlichung in Java 7 wird der invokedynamic
Opcode zusammen mit seinemjava.lang.invoke
Gepäck von dynamischen JVM-basierten Sprachen wie JRuby ziemlich häufig verwendet.
Indy wurde speziell entwickelt, um die Unterstützung dynamischer Sprachen zu verbessern, bietet aber noch viel mehr. Tatsächlich ist es überall dort einsetzbar, wo ein Sprachdesigner irgendeine Form von Dynamik benötigt, von dynamischer Akrobatik bis hin zu dynamischen Strategien!
Zum Beispiel werden die Java 8 Lambda-Ausdrücke tatsächlich mit implementiert invokedynamic
, obwohl Java eine statisch typisierte Sprache ist!
Seit geraumer Zeit unterstützt JVM vier Methodenaufruftypen: invokestatic
statische Methoden invokeinterface
aufrufen, Schnittstellenmethoden invokespecial
aufrufen, Konstruktoren super()
oder private Methoden invokevirtual
aufrufen und Instanzmethoden aufrufen.
Trotz ihrer Unterschiede haben diese Aufrufarten ein gemeinsames Merkmal: Wir können sie nicht mit unserer eigenen Logik bereichern . Im Gegenteil, invokedynamic
ermöglicht es uns, den Aufrufprozess nach Belieben zu booten. Dann kümmert sich die JVM darum, die Bootstrapped-Methode direkt aufzurufen.
Wenn JVM zum ersten Mal eine invokedynamic
Anweisung sieht , ruft sie eine spezielle statische Methode namens Bootstrap-Methode auf . Die Bootstrap-Methode ist ein Teil des Java-Codes, den wir geschrieben haben, um die eigentliche aufzurufende Logik vorzubereiten:
Dann gibt die Bootstrap-Methode eine Instanz von zurück java.lang.invoke.CallSite
. Dies CallSite
enthält einen Verweis auf die tatsächliche Methode, dhMethodHandle
.
Von nun an invokedynamic
überspringt JVM jedes Mal, wenn diese Anweisung erneut angezeigt wird, den langsamen Pfad und ruft die zugrunde liegende ausführbare Datei direkt auf. Die JVM überspringt weiterhin den langsamen Pfad, sofern sich nichts ändert.
Java 14 Records
bietet eine nette kompakte Syntax, um Klassen zu deklarieren, die dumme Dateninhaber sein sollen.
In Anbetracht dieser einfachen Aufzeichnung:
public record Range(int min, int max) {}
Der Bytecode für dieses Beispiel wäre ungefähr so:
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
In der Bootstrap-Methodentabelle :
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
Daher wird die Bootstrap- Methode für Datensätze aufgerufen, bootstrap
die sich in der java.lang.runtime.ObjectMethods
Klasse befindet. Wie Sie sehen können, erwartet diese Bootstrap-Methode die folgenden Parameter:
MethodHandles.Lookup
Darstellung des Suchkontexts (Der Ljava/lang/invoke/MethodHandles$Lookup
Teil).toString
, equals
, hashCode
, etc.) die Bootstrap Link wird. Wenn der Wert beispielsweise "ist" toString
, gibt bootstrap ein ConstantCallSite
(a CallSite
, das sich nie ändert) zurück, das auf die tatsächliche toString
Implementierung für diesen bestimmten Datensatz verweist .TypeDescriptor
für die Methode ( Ljava/lang/invoke/TypeDescriptor
Teil).Class<?>
das den Datensatzklassentyp darstellt. Es ist
Class<Range>
in diesem Fall.min;max
. H.MethodHandle
pro Komponente. Auf diese Weise kann die Bootstrap-Methode MethodHandle
basierend auf den Komponenten für diese bestimmte Methodenimplementierung eine erstellen .Die invokedynamic
Anweisung übergibt alle diese Argumente an die Bootstrap-Methode. Die Bootstrap-Methode gibt wiederum eine Instanz von zurück ConstantCallSite
. Dies enthält ConstantCallSite
einen Verweis auf die angeforderte Methodenimplementierung, z toString
.
Im Gegensatz zu den Reflection-APIs ist die java.lang.invoke
API sehr effizient, da die JVM alle Aufrufe vollständig durchsehen kann. Daher kann JVM alle Arten von Optimierungen anwenden, solange wir den langsamen Pfad so weit wie möglich vermeiden!
Zusätzlich zum Effizienzargument ist der invokedynamic
Ansatz aufgrund seiner Einfachheit zuverlässiger und weniger spröde .
Darüber hinaus ist der generierte Bytecode für Java Records unabhängig von der Anzahl der Eigenschaften. So weniger Bytecode und schnellere Startzeit.
Nehmen wir zum Schluss an, eine neue Version von Java enthält eine neue und effizientere Implementierung der Bootstrap-Methode. Mit invokedynamic
kann unsere App diese Verbesserung ohne Neukompilierung nutzen. Auf diese Weise haben wir eine Art Forward Binary Compatibility . Das ist auch die dynamische Strategie, über die wir gesprochen haben!
Zusätzlich zu Java Records wurde die Aufrufdynamik verwendet, um Funktionen wie Folgendes zu implementieren:
LambdaMetafactory
StringConcatFactory
meth.invoke(args)
. Wieinvokedynamic
passt das zusammenmeth.invoke
?