モジュール java.base
パッケージ java.lang.invoke

クラスMethodHandle

java.lang.Object
java.lang.invoke.MethodHandle
すべての実装されたインタフェース:
Constable

public abstract sealed class MethodHandle extends Object implements Constable
メソッド・ハンドルとは、ベースとなるメソッド、コンストラクタ、フィールド、または類似の低レベル操作に対する、直接実行可能な型付きの参照のことであり、オプションで引数や戻り値の変換も行います。 これらの変換は非常に一般的なものであり、変換挿入削除置換などのパターンを含みます。

メソッド・ハンドルの内容

メソッド・ハンドルは、そのパラメータと戻り値の型に従って動的にかつ強く型付けされています。 これらは、ベースとなるメソッドの名前や定義元のクラスで識別されません。 メソッド・ハンドルは、そのハンドル自身の型記述子に一致するシンボリック型記述子を使って呼び出す必要があります。

どのメソッド・ハンドルもtypeアクセサ経由で型記述子を報告します。 この型記述子はMethodTypeオブジェクトであり、その構造は一連のクラスになっていますが、その1つがメソッドの戻り値の型です(存在しない場合はvoid.class)。

メソッド・ハンドルの型によって、受け入れる呼出しのタイプや適用される変換の種類が制御されます。

メソッド・ハンドルには、invokeExactおよびinvokeと呼ばれる特殊なインボーカ・メソッドのペアが含まれています。 どちらのインボーカ・メソッドも、メソッド・ハンドルのベースとなるメソッド、コンストラクタ、フィールド、またはその他の操作(引数や戻り値の変換によって変更されたもの)に対する直接アクセスを提供します。 どちらのインボーカも、メソッド・ハンドル自身の型に厳密に一致する呼出しを受け入れます。 厳密でないプレーンなインボーカは、その他のさまざまな呼出しタイプも受け入れます。

メソッド・ハンドルは不変であり、可視の状態を一切持ちません。 当然、状態を公開しているベースとなるメソッドやデータにそれらをバインドすることは可能です。 Javaメモリー・モデルに関しては、どのメソッド・ハンドルもその(内部)フィールドがすべてfinal変数であるかのように振る舞います。 これは、アプリケーションから可視状態になったメソッド・ハンドルはすべて、常に完全な形式になっていることを意味します。 これは、メソッド・ハンドルがデータ競合時の共有変数を通じて公開された場合でも言えることです。

ユーザーによるメソッド・ハンドルのサブクラス化はできません。 実装はMethodHandleの内部サブクラスを作成することもしないこともありますが、これはObject.getClass操作を使って確認できます。 メソッド・ハンドルのクラス階層(存在する場合)は、時期や各種ベンダーの実装ごとに変わる可能性があるため、プログラマは、あるメソッド・ハンドルに関する結論をその特定のクラスから引き出すべきではありません。

メソッド・ハンドルのコンパイル

invokeExactまたはinvokeの名前を含むJavaメソッド呼出し式は、Javaソース・コードからメソッド・ハンドルを呼び出すことができます。 ソース・コードの視点から見ると、これらのメソッドは任意の引数を取ることができ、その結果を任意の戻り値の型にキャストできます。 これは形式上、インボーカ・メソッドに戻り値の型Objectと可変引数のObject引数を与えることで実現されていますが、これらのメソッドにはシグネチャ・ポリモーフィズムと呼ばれる追加の特性が備わっており、これにより、この自由な形式の呼出しがJVM実行スタックに直接接続されます。

仮想メソッドでは普通のことですが、invokeExactおよびinvokeに対するソース・レベルの呼出しは、invokevirtual命令にコンパイルされます。 通常とは異なる点として、コンパイラは実際の引数の型を記録する必要があり、引数に対するメソッド呼出し変換を実行することができません。 代わりに、独自の変換されていない型に従ってスタックにプッシュする命令を生成する必要があります。 メソッド・ハンドル・オブジェクト自体は、スタック上の引数の前にプッシュされます。 その後、コンパイラは、引数および戻り型を記述するシンボリック型記述子を使用してメソッド・ハンドルを呼び出すinvokevirtual命令を生成します。

完全なシンボリック型記述子を発行するには、コンパイラは戻り値の型も決定する必要があります。 これは、メソッド呼出し式(ある場合)のキャスト、または呼出しが式の場合はObject、または呼出しが文の場合はvoidに基づきます。 キャスト先はプリミティブ型でもかまいません(ただしvoidは不可)。

特殊なケースとして、キャストされていないnull引数にはjava.lang.Voidのシンボリック型記述子が与えられます。 Void型の参照はnull参照以外には存在しないため、型Voidのあいまいさが問題になることはありません。

メソッド・ハンドルの呼出し

invokevirtual命令は、最初に実行されたときに、命令内の名前を象徴的に解決し、メソッド・コールが静的に有効であることを検証することによってリンクされます。 これは、invokeExactinvokeへの呼び出しにも適用されます。 この場合、コンパイラによって出力されるシンボリック型記述子が正しい構文かどうかチェックされ、そこに含まれる名前が解決されます。 したがって、シンボリック型記述子が文法的に正しい形式であり、型が存在するかぎり、メソッド・ハンドルを呼び出すinvokevirtual命令は常にリンクされます。

invokevirtualがリンク後に実行される際には、まずJVMによってレシーバ・メソッド・ハンドルの型がチェックされ、それがシンボリック型記述子に一致することが確認されます。 型一致が失敗した場合、それは、呼出し元が呼び出そうとしているメソッドが、呼出し対象の個別のメソッド・ハンドル上には存在しないことを意味します。

invokeExactの場合、(シンボリック型名を解決したあとの)呼出しの型記述子が、レシーバ・メソッド・ハンドルのメソッド型と厳密に一致する必要があります。 厳密でないプレーンなinvokeの場合、解決済みの型記述子がレシーバのasTypeメソッドの有効な引数でなければいけません。 したがって、プレーンなinvokeの方が、invokeExactよりも許容範囲が広くなります。

型一致処理のあと、invokeExactの呼出しにより、メソッド・ハンドルのベースとなるメソッド(または場合によってはほかの動作)が直接的かつ即時に呼び出されます。

呼出し元によって指定されたシンボリック型記述子がメソッド・ハンドル自身の型に厳密に一致する場合、プレーンなinvokeの呼出しはinvokeExactの呼び出しと同じように動作します。 型の不一致が存在する場合、invokeは、asTypeを呼び出したかのようにレシーバ・メソッド・ハンドルの型の調整を試み、厳密な呼出しが可能なメソッド・ハンドルM2を取得します。 これにより、呼出し元と呼出し先の間での、メソッドの型に関するより強力なネゴシエーションが可能となります。

(ノート: 調整後のメソッド・ハンドルM2を直接監視することはできないため、実装においてその実体化を行う必要はありません。)

呼出しのチェック

典型的なプログラムでは、メソッド・ハンドルの型一致処理は通常成功します。 しかし、一致が失敗した場合は、JVMからWrongMethodTypeExceptionが直接的に(invokeExactの場合)またはasTypeの呼出しが失敗した場合のように間接的に(invokeの場合)スローされます。

したがって、静的に型付けされたプログラムではリンケージ・エラーとして示されるメソッド型の不一致が、メソッド・ハンドルを使用するプログラムでは動的なWrongMethodTypeExceptionとして示される可能性があります。

メソッド型には"live" Classオブジェクトが含まれているため、メソッド型の一致では型名とクラス・ローダーの両方が考慮されます。 したがって、メソッド・ハンドルMが、あるクラス・ローダーL1で作成され、別のL2で使用される場合でも、L2で解決された呼出し元のシンボリック型記述子の一致処理は、L1で解決された元の呼出し先メソッドのシンボリック型記述子に対して行われるため、メソッド・ハンドル呼出しは型保証されます。 L1での解決は、Mが作成されてその型が割り当てられるときに行われるのに対し、L2での解決は、invokevirtual命令のリンク時に行われます。

型記述子チェックとは別に、基礎となるメソッドを呼び出すメソッド・ハンドルの機能は制限されません。 ある非publicメソッドのメソッド・ハンドルがそのメソッドへのアクセスを持つクラスによって作成された場合、結果となるハンドルは、そのハンドルへの参照を受け取った任意の呼出し元によって任意の場所で使用できます。

リフレクション・メソッドが呼び出されるたびにアクセス・チェックが行われるCore Reflection APIと異なり、メソッド・ハンドルのアクセス・チェックはメソッド・ハンドルの作成時に実行されます。 ldc (以下を参照)の場合は、定数メソッド・ハンドルのベースとなる定数プール・エントリのリンク処理の一部として、アクセス・チェックが実行されます。

したがって、非publicメソッドへのハンドルや非publicクラス内のメソッドへのハンドルは、一般に非公開にしておくべきです。 信頼できないコードがそれらを使用しても問題が発生しない場合を除き、それらを信頼できないコードに渡さないようにしてください。

メソッド・ハンドルの作成

Javaコードは、そのコードからアクセス可能な任意のメソッド、コンストラクタ、またはフィールドに直接アクセスするメソッド・ハンドルを作成できます。 これは、MethodHandles.Lookupというリフレクティブな機能ベースのAPIを介して行われます。 たとえば、静的メソッド・ハンドルは、Lookup.findStaticから取得できます。 Core Reflection APIオブジェクトからの変換メソッド(Lookup.unreflectなど)もあります。

アクセス可能なフィールド、メソッド、およびコンストラクタに対応するメソッド・ハンドルはクラスや文字列の場合と同じく、クラス・ファイルの定数プール内で直接、ldcバイト・コードによってロードされる定数として表現することもできます。 新しいタイプの定数プール・エントリCONSTANT_MethodHandleは、関連するCONSTANT_MethodrefCONSTANT_InterfaceMethodref、またはCONSTANT_Fieldref定数プール・エントリを直接参照します。 (メソッド・ハンドル定数の詳細は、Java Virtual Machine仕様の4.4.8および5.4.3.5の項を参照してください。)

可変引数修飾子ビット(0x0080)を持つメソッドまたはコンストラクタからのルックアップまたは定数ロードによって生成されるメソッド・ハンドルは、asVarargsCollectorまたはwithVarargsを使用して定義されたかのように、対応する可変引数を持ちます。

メソッド参照は静的メソッド、静的でないメソッドのいずれかを参照できます。 静的でない場合、メソッド・ハンドルの型には、ほかのすべての引数の前に追加された明示的なレシーバ引数が含まれます。 メソッド・ハンドルの型に含まれる最初のレシーバ引数の型は、メソッドが最初に要求されたクラスに従って決定されます。 (たとえば、ldc経由で取得された静的でないメソッド・ハンドルの場合、レシーバの型は、定数プール・エントリで指定されたクラスになります。)

メソッド・ハンドル定数は、それに対応するバイト・コード命令と同じリンク時アクセス・チェックの対象であり、ldc命令によって対応するリンケージ・エラーがスローされます(バイト・コードの動作でそのようなエラーがスローされる場合)。

この結果として、protectedメンバーへのアクセスはアクセスするクラスかそのいずれかのサブクラスのレシーバのみに制限され、アクセスするクラスは次にはprotectedメンバーの定義クラスのサブクラス(パッケージの兄弟)になる必要があります。 メソッド参照が現在のパッケージ外部にあるクラスの静的でないprotectedメソッドまたはフィールドを参照する場合、レシーバ引数はアクセスするクラスの型にナロー変換されます。

仮想メソッドへのメソッド・ハンドルを呼び出した場合、メソッドのルックアップは常にレシーバ(つまり最初の引数)内で行われます。

特定の仮想メソッド実装への非仮想メソッド・ハンドルも作成できます。 これらは、レシーバの型に基づく仮想ルックアップを実行しません。 そのようなメソッド・ハンドルは、同じメソッドに対するinvokespecial命令の効果をシミュレートします。 また、非仮想メソッド・ハンドルを作成して、プライベート・メソッド(適用可能)にあるinvokevirtualまたはinvokeinterfaceイン・スト・ラク・ションの影響をシミュレートすることもできます。

使用例

次にいくつかの使用例を示します。
Object x, y; String s; int i;
MethodType mt; MethodHandle mh;
MethodHandles.Lookup lookup = MethodHandles.lookup();
// mt is (char,char)String
mt = MethodType.methodType(String.class, char.class, char.class);
mh = lookup.findVirtual(String.class, "replace", mt);
s = (String) mh.invokeExact("daddy",'d','n');
// invokeExact(Ljava/lang/String;CC)Ljava/lang/String;
assertEquals(s, "nanny");
// weakly typed invocation (using MHs.invoke)
s = (String) mh.invokeWithArguments("sappy", 'p', 'v');
assertEquals(s, "savvy");
// mt is (Object[])List
mt = MethodType.methodType(java.util.List.class, Object[].class);
mh = lookup.findStatic(java.util.Arrays.class, "asList", mt);
assert(mh.isVarargsCollector());
x = mh.invoke("one", "two");
// invoke(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object;
assertEquals(x, java.util.Arrays.asList("one","two"));
// mt is (Object,Object,Object)Object
mt = MethodType.genericMethodType(3);
mh = mh.asType(mt);
x = mh.invokeExact((Object)1, (Object)2, (Object)3);
// invokeExact(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
assertEquals(x, java.util.Arrays.asList(1,2,3));
// mt is ()int
mt = MethodType.methodType(int.class);
mh = lookup.findVirtual(java.util.List.class, "size", mt);
i = (int) mh.invokeExact(java.util.Arrays.asList(1,2,3));
// invokeExact(Ljava/util/List;)I
assert(i == 3);
mt = MethodType.methodType(void.class, String.class);
mh = lookup.findVirtual(java.io.PrintStream.class, "println", mt);
mh.invokeExact(System.out, "Hello, world.");
// invokeExact(Ljava/io/PrintStream;Ljava/lang/String;)V
invokeExactまたはプレーンなinvokeに対する上記の各呼出しは、直後のコメントに示しているシンボリック型記述子を持つ単一のinvokevirtual命令を生成します。 これらの例で、ヘルパー・メソッドassertEqualsは、その引数でObjects.equalsを呼び出し、その結果がtrueであることを表明するメソッドであると想定されています。

例外

メソッドinvokeExactinvokeThrowableをスローするように宣言されていますが、これは、メソッド・ハンドルからスローできるものについて、静的な制限は一切ないことを示すためです。 JVMはチェック例外と非チェック例外を区別しない(もちろん、それらのクラスによる区別は行う)ため、チェック例外をメソッド・ハンドル呼出しに帰することの、バイト・コードの構造への影響は特に存在しません。 しかし、Javaソース・コードでは、メソッド・ハンドル呼出しを実行するメソッドは、Throwableを明示的にスローするか、あるいはすべてのスロー可能オブジェクトをローカルでキャッチし、コンテキスト内で正しいもののみをスローし直し、不正なものはラップする必要があります。

シグネチャ・ポリモーフィズム

invokeExactとプレーンなinvokeの通常とは異なるコンパイル動作やリンク動作には、シグネチャ・ポリモーフィズムという用語が使用されます。 Java言語仕様で定義されているように、シグネチャ・ポリモーフィズム・メソッドとは、広範な呼出しシグネチャや戻り値の型のいずれでも動作できるメソッドのことです。

ソース・コードに含まれるシグネチャ・ポリモーフィズム・メソッドの呼出しは、要求されたシンボリック型記述子がどのようなものであってもコンパイルされます。 Javaコンパイラは通常どおり、指定されたシンボリック型記述子を持つ、指定されたメソッドに対するinvokevirtual命令を出力します。 通常と異なる部分は、シンボリック型記述子が、メソッド宣言からではなく実際の引数と戻り値の型から派生される点です。

シグネチャ・ポリモーフィズム呼出しを含むバイト・コードがJVMで処理されるとき、そのような呼出しはすべて、シンボリック型記述子がどのようなものであっても正常にリンクされます。 (ほかの場所で説明したように、JVMは型保証を保持するため、そのような呼出しを適切な動的型チェックで保護します。)

バイトコード・ジェネレータ(コンパイラのバックエンドも含む)は、それらのメソッドに対する変換されていないシンボリック型記述子を出力する必要があります。 シンボリック・リンケージを決定するツールは、そのような変換されていない記述子をリンケージ・エラーを報告しないで受け入れる必要があります。

メソッド・ハンドルとCore Reflection API間の相互運用

Lookup APIのファクトリ・メソッドを使えば、Core Reflection APIオブジェクトとして表現された任意のクラス・メンバーを、同等の動作を備えたメソッド・ハンドルに変換できます。 たとえば、リフレクションのMethodをメソッド・ハンドルに変換するには、Lookup.unreflectを使用します。 結果となるメソッド・ハンドルは一般に、ベースとなるクラス・メンバーへのより直接的かつ効率的なアクセス機能を提供します。

特殊な場合として、このクラス内のシグネチャ・ポリモーフィズム・メソッドであるinvokeExactまたはプレーンなinvokeをCore Reflection APIを使って確認した場合、それらは通常の非ポリモーフィズム・メソッドとして示されます。 Class.getDeclaredMethodで示されるそれらのリフレクション表現は、このAPIでの特殊なステータスの影響を受けません。 たとえば、Method.getModifiersでは、同様に宣言されたすべてのメソッドで必要になる修飾子ビット(この場合はnativeビットやvarargsビットなど)が厳密に報告されます。

これらのメソッド(をリフレクトしたもの)は、ほかのリフレクトされたメソッドと同じく、java.lang.reflect.Method.invokeを使って呼び出すことができます。 ただし、そのようなリフレクション呼出しを行っても、メソッド・ハンドルは呼び出されません。 そのような呼出しに必要な引数(Object[]型の単一の引数)を渡してもその引数は無視され、UnsupportedOperationExceptionがスローされます。

invokevirtual命令は、メソッド・ハンドルを任意のシンボリック型記述子の下でネイティブに呼び出すことができるため、このリフレクション表示は、バイト・コード経由でのこれらのメソッドの通常の表示と矛盾します。 したがって、これら2つのネイティブ・メソッドをClass.getDeclaredMethodでリフレクション表示したものは、プレースホルダー専用とみなすことができます。

特定の型記述子のインボーカ・メソッドを取得するには、MethodHandles.exactInvokerまたはMethodHandles.invokerを使用します。 Lookup.findVirtual APIも、指定された任意の型記述子に対する、invokeExactまたはプレーンなinvokeを呼び出すメソッド・ハンドルを返すことができます。

メソッド・ハンドルとJavaジェネリックス間の相互運用

Javaジェネリック型を使って宣言されたメソッド、コンストラクタ、またはフィールドのメソッド・ハンドルを取得できます。 コア・リフレクションAPIと同様に、メソッド・ハンドルのタイプはソース・レベル・タイプの紀元から構築されます。 メソッド・ハンドルを呼び出す際には、その引数の型や戻り値のキャストの型がジェネリックの型または型インスタンスになる可能性があります。 これが起こった場合、コンパイラは、invokevirtual命令のシンボリック型記述子を構築する際に、それらの型を対応するイレイジャで置き換えます。

メソッド・ハンドルの関数形式の型は、Javaのパラメータ化された型(ジェネリック型)を使って表現されませんが、これは、関数形式の型とパラメータ化されたJava型との間に3つの不一致が存在しているからです。

  • メソッド型は、引数なしから許可される引数の最大数まで、さまざまな引数カウントが可能です。 ジェネリックスは可変個引数でないため、これを表現することができません。
  • メソッド型にはプリミティブ型の引数を指定できますが、Javaジェネリック型はプリミティブ型に対応できません。
  • メソッド・ハンドルに対する高階関数(コンビネータ)は通常、複数の引数長を持つ関数型など、広範な関数型にわたって汎用的です。 そのようなジェネリック特性をJavaの型パラメータで表現することは不可能です。

引数の制限

JVMは、あらゆる種類のすべてのメソッドおよびコンストラクタに対して、255個のスタックされた引数という絶対制限を適用します。 この制限は、一部のクラスでより制限的になることがあります。
  • longまたはdouble引数は、(引数カウント制限のために) 2つの引数スロットとしてカウントします。
  • 非staticメソッドは、メソッドが呼び出されるオブジェクトの余分な引数を消費します。
  • コンストラクタは、構築されるオブジェクトの余分な引数を消費します。
  • メソッド・ハンドルのinvokeメソッド(または他のシグネチャ・ポリモーフィズム・メソッド)は、非仮想であるため、非仮想レシーバ・オブジェクト以外にメソッド・ハンドル自体の余分な引数を消費します。
これらの制限は、ある種のメソッド・ハンドルを作成できないことを意味します。スタックされた引数に関するJVM制限だけが原因です。 たとえば、static JVMメソッドが正確に255個の引数を受け入れる場合、それのメソッド・ハンドルは作成できません。 不可能なメソッド型でメソッド・ハンドルを作成しようとすると、IllegalArgumentExceptionがスローされます。 特に、メソッド・ハンドルの型は、正確に最大255個の引数カウントを持つことはできません。
導入されたバージョン:
1.7
関連項目: