回答:
これは新しいJVM命令で、コンパイラーは以前よりも緩い仕様でメソッドを呼び出すコードを生成できます。「ダックタイピング」とは何かを知っている場合、invokedynamicは基本的にダックタイピングを可能にします。Javaプログラマーができることはそれほど多くありません。ただし、ツール作成者であれば、それを使用して、より柔軟で効率的なJVMベースの言語を構築できます。ここにたくさんの詳細を与える本当に甘いブログ投稿があります。
MethodHandle
、これは本当に同じようなことですが、柔軟性がはるかに高くなっています。しかし、これらすべての真の力は、Java言語に加えてではなく、本質的に動的な他の言語をサポートするJVM自体の機能にあります。
invokedynamic
て翻訳し、パフォーマンスを向上させているようです(導入する前にほとんど唯一の選択肢であった匿名の内部クラスでそれらをラップする場合と比較してinvokedynamic
)。ほとんどの場合、JVM上の多くの関数型プログラミング言語は、内部クラスではなく、これにコンパイルすることを選択します。
少し前に、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#の動的型を担当するNeal Gafterは、SUNからMSに移籍しました。したがって、同じことがSUNの内部で議論されたと考えることは不合理ではありません。
その後すぐに覚えています
InvokeDynamic duck = obj;
duck.quack();
残念ながら、この機能はJava 7にはありません。非常にがっかりしました。Javaプログラマーにとって、彼らはプログラムで活用する簡単な方法はありませんinvokedynamic
。
invokedynamic
Javaプログラマーが使用することを意図したものではありません。IMOは、Javaの哲学にまったく適合しません。これは、Java以外の言語のJVM機能として追加されました。
invokedynamicを続ける前に理解しておくべき2つの概念があります。
1.静的型とダイナミン型
Static-コンパイル時に型チェックをプリフォームします(例:Java)
動的 -実行時に型チェックをプリフォームします(例:JavaScript)
タイプチェックは、プログラムがタイプセーフであることを確認するプロセスです。これは、クラス変数とインスタンス変数、メソッドパラメータ、戻り値、およびその他の変数のタイプ情報をチェックすることです。たとえば、Javaはコンパイル時にint、Stringなどを認識しますが、JavaScriptのオブジェクトのタイプは実行時にのみ決定できます
2.強い型付けと弱い型付け
強力 -操作に提供される値のタイプ(Javaなど)に対する制限を指定します
弱 -引数が互換性のないタイプ(例:Visual Basic)の場合、操作の引数を変換(キャスト)します。
Javaが静的および弱く型付けされていることを知って、動的および強く型付けされた言語をJVMにどのように実装しますか?
invokedynamicは、プログラムのコンパイル後に、メソッドまたは関数の最も適切な実装を選択できるランタイムシステムを実装します。
例: (a + b)があり、コンパイル時に変数a、bについて何も知らない場合、invokedynamicはこの操作を実行時にJavaの最も適切なメソッドにマップします。たとえば、a、bが文字列であることが判明した場合は、method(String a、String b)を呼び出します。a、bがintであることが判明した場合は、method(int a、int b)を呼び出します。
invokedynamicはJava 7で導入されました。
Java Recordsの記事の一部として、私はInoke Dynamicの背後にある動機について明確に述べました。インディの大まかな定義から始めましょう。
Invoke Dynamic(別名Indy)は、動的タイプ言語のJVMサポートを強化することを目的としたJSR 292の一部でした。Java 7での最初のリリース後、invokedynamic
オペコードとそのjava.lang.invoke
荷物は、JRubyなどの動的なJVMベースの言語で非常に広く使用されています。
動的言語サポートを強化するために特別に設計されたindyですが、それだけではありません。実際のところ、言語設計者が動的型アクロバットから動的戦略まで、あらゆる形式の動的性を必要とするところはどこでも使用するのに適しています!
たとえば、Javaは静的に型付けされた言語ですが、Java 8のラムダ式は実際にはを使用invokedynamic
して実装されています。
かなり前から、JVMは4つのメソッド呼び出しタイプ(invokestatic
静的メソッドのinvokeinterface
呼び出し、インターフェースメソッドのinvokespecial
呼び出し、コンストラクターの呼び出し、super()
プライベートメソッドのinvokevirtual
呼び出し、およびインスタンスメソッドの呼び出し)をサポートしていました。
それらの違いにもかかわらず、これらの呼び出しタイプは1つの共通の特性を共有します。つまり、独自のロジックでそれらを強化することはできません。それどころか、invokedynamic
私たちは好きな方法で呼び出しプロセスをブートストラップすることができます。次に、JVMがBootstrappedメソッドを直接呼び出します。
JVMが初めてinvokedynamic
命令を見ると、Bootstrapメソッドと呼ばれる特別な静的メソッドを呼び出します。ブートストラップメソッドは、実際に呼び出されるロジックを準備するために作成した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
場合、ブートストラップは、この特定のレコードの実際の実装を指すConstantCallSite
(CallSite
変更されない)を返しtoString
ます。TypeDescriptor
方法(のためのLjava/lang/invoke/TypeDescriptor
部分)。Class<?>
、Recordクラスタイプを表します。それはだ
Class<Range>
、この場合には。min;max
。MethodHandle
コンポーネントごと。このようにして、ブートストラップメソッドはMethodHandle
、この特定のメソッド実装のコンポーネントに基づいてを作成できます。このinvokedynamic
命令は、これらすべての引数をブートストラップメソッドに渡します。次に、Bootstrapメソッドはのインスタンスを返しますConstantCallSite
。これConstantCallSite
は、要求されたメソッド実装への参照を保持していますtoString
。
Reflection APIとは対照java.lang.invoke
的に、JVMはすべての呼び出しを完全に確認できるため、APIは非常に効率的です。したがって、低速パスを可能な限り回避する限り、JVMはあらゆる種類の最適化を適用する可能性があります。
効率の議論に加えて、このinvokedynamic
アプローチは、その単純さのために、より信頼性が高く、もろくなりません。
さらに、Javaレコード用に生成されたバイトコードは、プロパティの数に依存しません。したがって、バイトコードが少なくなり、起動時間が速くなります。
最後に、新しいバージョンのJavaに、新しく効率的なブートストラップメソッドの実装が含まれているとします。を使用するinvokedynamic
と、アプリはこの改善を再コンパイルなしで利用できます。このように、ある種の前方バイナリ互換性があります。また、それは私たちが話していた動的な戦略です!
Javaレコードに加えて、動的呼び出しは次のような機能を実装するために使用されています。
LambdaMetafactory
StringConcatFactory
meth.invoke(args)
。では、どのようにinvokedynamic
適合しmeth.invoke
ますか?