public abstract class MethodHandle extends Object
どのメソッド・ハンドルもtype
アクセサ経由で型記述子を報告します。この型記述子はMethodType
オブジェクトであり、その構造は一連のクラスになっていますが、その1つがメソッドの戻り値の型です(存在しない場合はvoid.class
)。
メソッド・ハンドルの型によって、受け入れる呼出しのタイプや適用される変換の種類が制御されます。
メソッド・ハンドルには、invokeExact
およびinvoke
と呼ばれる特殊なインボーカ・メソッドのペアが含まれています。どちらのインボーカ・メソッドも、メソッド・ハンドルのベースとなるメソッド、コンストラクタ、フィールド、またはその他の操作(引数や戻り値の変換によって変更されたもの)に対する直接アクセスを提供します。どちらのインボーカも、メソッド・ハンドル自身の型に厳密に一致する呼出しを受け入れます。厳密でないプレーンなインボーカは、その他のさまざまな呼出しタイプも受け入れます。
メソッド・ハンドルは不変であり、可視の状態を一切持ちません。当然、状態を公開しているベースとなるメソッドやデータにそれらをバインドすることは可能です。Javaメモリー・モデルに関しては、どのメソッド・ハンドルもその(内部)フィールドがすべてfinal変数であるかのように振る舞います。これは、アプリケーションから可視状態になったメソッド・ハンドルはすべて、常に完全な形式になっていることを意味します。これは、メソッド・ハンドルがデータ競合時の共有変数を通じて公開された場合でも言えることです。
ユーザーによるメソッド・ハンドルのサブクラス化はできません。実装はMethodHandle
の内部サブクラスを作成することもしないこともありますが、これはObject.getClass
操作を使って確認できます。メソッド・ハンドルのクラス階層(存在する場合)は、時期や各種ベンダーの実装ごとに変わる可能性があるため、プログラマは、あるメソッド・ハンドルに関する結論をその特定のクラスから引き出すべきではありません。
invokeExact
またはinvoke
の名前を含むJavaメソッド呼出し式は、Javaソース・コードからメソッド・ハンドルを呼び出すことができます。ソース・コードの視点から見ると、これらのメソッドは任意の引数を取ることができ、その結果を任意の戻り値の型にキャストできます。これは形式上、インボーカ・メソッドに戻り値の型Object
と可変引数のObject
引数を与えることで実現されていますが、これらのメソッドにはシグネチャ・ポリモーフィズムと呼ばれる追加の特性が備わっており、これにより、この自由な形式の呼出しがJVM実行スタックに直接接続されます。
仮想メソッドでは普通のことですが、invokeExact
およびinvoke
に対するソース・レベルの呼出しは、invokevirtual
命令にコンパイルされます。通常とは異なる点として、コンパイラは実際の引数の型を記録する必要があり、引数に対するメソッド呼出し変換を実行することができません。代わりに、それらを独自の変換されていない型に従ってスタック上にプッシュする必要があります。メソッド・ハンドル・オブジェクト自体は、スタック上の引数の前にプッシュされます。その後コンパイラは、引数や戻り値の型を記述するシンボリック型記述子を使ってメソッド・ハンドルを呼び出します。
完全なシンボリック型記述子を発行するには、コンパイラは戻り値の型も決定する必要があります。これは、メソッド呼出し式のキャストが存在する場合はそれに基づき、呼出しが式の場合はObject
、呼出しが文の場合はvoid
になります。キャスト先はプリミティブ型でもかまいません(ただしvoid
は不可)。
特殊なケースとして、キャストされていないnull
引数にはjava.lang.Void
のシンボリック型記述子が与えられます。Void
型の参照はnull参照以外には存在しないため、型Void
のあいまいさが問題になることはありません。
invokevirtual
命令がはじめて実行されるときにそのリンクが行われますが、そのために、命令に含まれる名前がシンボルとして解決され、メソッド呼出しが静的に正しいことが検証されます。これは、invokeExact
とinvoke
の呼出しについて言えることです。この場合、コンパイラから出力されたシンボリック型記述子の構文が正しいかチェックされ、それに含まれる名前が解決されます。したがって、シンボリック型記述子が文法的に正しい形式であり、型が存在するかぎり、メソッド・ハンドルを呼び出すinvokevirtual
命令は常にリンクされます。
invokevirtual
がリンク後に実行される際には、まずJVMによってレシーバ・メソッド・ハンドルの型がチェックされ、それがシンボリック型記述子に一致することが確認されます。型一致が失敗した場合、それは、呼出し元が呼び出そうとしているメソッドが、呼出し対象の個別のメソッド・ハンドル上には存在しないことを意味します。
invokeExact
の場合、(シンボリック型名を解決したあとの)呼出しの型記述子が、レシーバ・メソッド・ハンドルのメソッド型と厳密に一致する必要があります。厳密でないプレーンなinvoke
の場合、解決済みの型記述子がレシーバのasType
メソッドの有効な引数でなければいけません。したがって、プレーンなinvoke
の方が、invokeExact
よりも許容範囲が広くなります。
型一致処理のあと、invokeExact
の呼出しにより、メソッド・ハンドルのベースとなるメソッド(または場合によってはほかの動作)が直接的かつ即時に呼び出されます。
呼出し元によって指定されたシンボリック型記述子がメソッド・ハンドル自身の型に厳密に一致する場合、プレーンなinvoke
の呼出しはinvokeExact
の呼び出しと同じように動作します。型の不一致が存在する場合、invoke
は、asType
を呼び出したかのようにレシーバ・メソッド・ハンドルの型の調整を試み、厳密な呼出しが可能なメソッド・ハンドルM2
を取得します。これにより、呼出し元と呼出し先の間での、メソッドの型に関するより強力なネゴシエーションが可能となります。
(注: 調整後のメソッド・ハンドルM2
を直接監視することはできないため、実装においてその実体化を行う必要はありません。)
WrongMethodTypeException
が直接的に(invokeExact
の場合)またはasType
の呼出しが失敗した場合のように間接的に(invoke
の場合)スローされます。
したがって、静的に型付けされたプログラムではリンケージ・エラーとして示されるメソッド型の不一致が、メソッド・ハンドルを使用するプログラムでは動的なWrongMethodTypeException
として示される可能性があります。
メソッド型には「ライブ」のClass
オブジェクトが含まれているため、メソッド型の一致処理では、型の名前とクラス・ローダーの両方が考慮されます。したがって、メソッド・ハンドルM
が、あるクラス・ローダーL1
で作成され、別のL2
で使用される場合でも、L2
で解決された呼出し元のシンボリック型記述子の一致処理は、L1
で解決された元の呼出し先メソッドのシンボリック型記述子に対して行われるため、メソッド・ハンドル呼出しは型保証されます。L1
での解決は、M
が作成されてその型が割り当てられるときに行われるのに対し、L2
での解決は、invokevirtual
命令のリンク時に行われます。
型記述子のチェックのほかに、ベースとなるメソッドを呼び出すメソッド・ハンドルの機能を制限するものはありません。ある非publicメソッドのメソッド・ハンドルがそのメソッドへのアクセスを持つクラスによって作成された場合、結果となるハンドルは、そのハンドルへの参照を受け取った任意の呼出し元によって任意の場所で使用できます。
リフレクション・メソッドが呼び出されるたびにアクセス・チェックが行われるCore Reflection APIと異なり、メソッド・ハンドルのアクセス・チェックはメソッド・ハンドルの作成時に実行されます。ldc
(以下を参照)の場合は、定数メソッド・ハンドルのベースとなる定数プール・エントリのリンク処理の一部として、アクセス・チェックが実行されます。
したがって、非publicメソッドへのハンドルや非publicクラス内のメソッドへのハンドルは、一般に非公開にしておくべきです。信頼できないコードがそれらを使用しても問題が発生しない場合を除き、それらを信頼できないコードに渡さないようにしてください。
MethodHandles.Lookup
と呼ばれるリフレクトな機能ベースAPIを介して行われます。たとえば、静的メソッド・ハンドルをLookup.findStatic
から取得できます。Core Reflection APIオブジェクトからの変換メソッド(Lookup.unreflect
など)もあります。
アクセス可能なフィールド、メソッド、およびコンストラクタに対応するメソッド・ハンドルはクラスや文字列の場合と同じく、クラス・ファイルの定数プール内で直接、ldc
バイト・コードによってロードされる定数として表現することもできます。新しいタイプの定数プール・エントリCONSTANT_MethodHandle
は、関連するCONSTANT_Methodref
、CONSTANT_InterfaceMethodref
、またはCONSTANT_Fieldref
定数プール・エントリを直接参照します。(メソッド・ハンドル定数の詳細は、『Java Virtual Machine Specification』のセクション4.4.8および5.4.3.5を参照してください。)
可変引数修飾子ビット(0x0080
)を持つメソッドまたはコンストラクタからルックアップや定数ロードによって生成されたメソッド・ハンドルは、asVarargsCollector
の支援によって定義されたかのように、対応する可変引数を持ちます。
メソッド参照は静的メソッド、静的でないメソッドのいずれかを参照できます。静的でない場合、メソッド・ハンドルの型には、ほかのすべての引数の前に追加された明示的なレシーバ引数が含まれます。メソッド・ハンドルの型に含まれる最初のレシーバ引数の型は、メソッドが最初に要求されたクラスに従って決定されます。(たとえば、ldc
経由で取得された静的でないメソッド・ハンドルの場合、レシーバの型は、定数プール・エントリで指定されたクラスになります。)
メソッド・ハンドル定数は、それに対応するバイト・コード命令と同じリンク時アクセス・チェックの対象であり、ldc
命令によって対応するリンケージ・エラーがスローされます(バイト・コードの動作でそのようなエラーがスローされる場合)。
この結果として、protectedメンバーへのアクセスはアクセスするクラスかそのいずれかのサブクラスのレシーバのみに制限され、アクセスするクラスは次にはprotectedメンバーの定義クラスのサブクラス(パッケージの兄弟)になる必要があります。メソッド参照が現在のパッケージ外部にあるクラスの静的でないprotectedメソッドまたはフィールドを参照する場合、レシーバ引数はアクセスするクラスの型にナロー変換されます。
仮想メソッドへのメソッド・ハンドルを呼び出した場合、メソッドのルックアップは常にレシーバ(つまり最初の引数)内で行われます。
特定の仮想メソッド実装への非仮想メソッド・ハンドルも作成できます。これらは、レシーバの型に基づく仮想ルックアップを実行しません。そのようなメソッド・ハンドルは、同じメソッドに対するinvokespecial
命令の効果をシミュレートします。
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であることを表明するメソッドであると想定されています。
invokeExact
とinvoke
はThrowable
をスローするように宣言されていますが、これは、メソッド・ハンドルからスローできるものについて、静的な制限は一切ないことを示すためです。JVMはチェック例外と非チェック例外を区別しない(もちろん、それらのクラスによる区別は行う)ため、チェック例外をメソッド・ハンドル呼出しに帰することの、バイト・コードの構造への影響は特に存在しません。しかし、Javaソース・コードでは、メソッド・ハンドル呼出しを実行するメソッドは、Throwable
を明示的にスローするか、あるいはすべてのスロー可能オブジェクトをローカルでキャッチし、コンテキスト内で正しいもののみをスローし直し、不正なものはラップする必要があります。
invokeExact
とプレーンなinvoke
の通常とは異なるコンパイル動作やリンク動作には、シグネチャ・ポリモーフィズムという用語が使用されます。Java言語仕様で定義されているように、シグネチャ・ポリモーフィズム・メソッドとは、広範な呼出しシグネチャや戻り値の型のいずれでも動作できるメソッドのことです。
ソース・コードに含まれるシグネチャ・ポリモーフィズム・メソッドの呼出しは、要求されたシンボリック型記述子がどのようなものであってもコンパイルされます。Javaコンパイラは通常どおり、指定されたシンボリック型記述子を持つ、指定されたメソッドに対するinvokevirtual
命令を出力します。通常と異なる部分は、シンボリック型記述子が、メソッド宣言からではなく実際の引数と戻り値の型から派生される点です。
シグネチャ・ポリモーフィズム呼出しを含むバイト・コードがJVMで処理されるとき、そのような呼出しはすべて、シンボリック型記述子がどのようなものであっても正常にリンクされます。(ほかの場所で説明したように、JVMは型保証を保持するため、そのような呼出しを適切な動的型チェックで保護します。)
バイトコード・ジェネレータ(コンパイラのバックエンドも含む)は、それらのメソッドに対する変換されていないシンボリック型記述子を出力する必要があります。シンボリック・リンケージを決定するツールは、そのような変換されていない記述子をリンケージ・エラーを報告しないで受け入れる必要があります。
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
を呼び出すメソッド・ハンドルを返すことができます。
invokevirtual
命令のシンボリック型記述子を構築する際に、それらの型を対応するイレイジャで置き換えます。
メソッド・ハンドルの関数形式の型は、Javaのパラメータ化された型(ジェネリック型)を使って表現されませんが、これは、関数形式の型とパラメータ化されたJava型との間に3つの不一致が存在しているからです。
long
またはdouble
引数は、(引数カウント制限のために) 2つの引数スロットとしてカウントします。
invoke
メソッド(または他のシグネチャ・ポリモーフィズム・メソッド)は、非仮想であるため、非仮想レシーバ・オブジェクト以外にメソッド・ハンドル自体の余分な引数を消費します。
IllegalArgumentException
がスローされます。特に、メソッド・ハンドルの型は、正確に最大255個の引数カウントを持つことはできません。MethodType
, MethodHandles
修飾子と型 | メソッドと説明 |
---|---|
MethodHandle |
asCollector(Class<?> arrayType, int arrayLength)
末尾の指定された数の定位置引数を受け取り、それらを集めて1つの配列引数に格納するような、配列収集メソッド・ハンドルを作成します。
|
MethodHandle |
asFixedArity()
固定引数カウント・メソッド・ハンドル(その他の点では現在のメソッド・ハンドルと同等のもの)を作成します。
|
MethodHandle |
asSpreader(Class<?> arrayType, int arrayLength)
末尾の1つの配列引数を受け取り、その要素を複数の定位置引数に分配するような、配列分配メソッド・ハンドルを作成します。
|
MethodHandle |
asType(MethodType newType)
現在のメソッド・ハンドルの型を新しい型に適応させるアダプタ・メソッド・ハンドルを生成します。
|
MethodHandle |
asVarargsCollector(Class<?> arrayType)
末尾の任意の数の定位置引数を受け取り、それらを集めて1つの配列引数に格納できるような、可変引数アダプタを作成します。
|
MethodHandle |
bindTo(Object x)
値
x をメソッド・ハンドルの最初の引数にバインドしますが、その呼出しは行いません。 |
Object |
invoke(Object... args)
メソッド・ハンドルを呼び出しますが、その際、呼出し元のどのような型記述子でも許可され、必要に応じて引数や戻り値の変換も実行されます。
|
Object |
invokeExact(Object... args)
メソッド・ハンドルを呼び出し、その際、呼出し元のどのような型記述子でも許可されますが、型は厳密に一致する必要があります。
|
Object |
invokeWithArguments(List<?> arguments)
指定された配列内の引数をメソッド・ハンドルに渡しながら可変引数呼出しを実行しますが、これは、
Object 型のみについて言及し、引数の配列の長さに等しい引数長を持つようなコール・サイトから、厳密でないinvoke を実行するのと同じです。 |
Object |
invokeWithArguments(Object... arguments)
指定されたリスト内の引数をメソッド・ハンドルに渡して、可変引数呼出しを実行します。これは、
Object 型のみを言及し、引数カウントが引数リストの長さであるコール・サイトから、厳密でないinvoke を使用するのと同じです。 |
boolean |
isVarargsCollector()
このメソッド・ハンドルが可変引数呼出しをサポートしているかどうかを判定します。
|
String |
toString()
メソッド・ハンドルの文字列表現を返しますが、これは、文字列
"MethodHandle" で始まり、メソッド・ハンドルの型の文字列表現で終わります。 |
MethodType |
type()
このメソッド・ハンドルの型を報告します。
|
public MethodType type()
invokeExact
によるこのメソッド・ハンドルのすべての呼出しは、この型と厳密に一致する必要があります。public final Object invokeExact(Object... args) throws Throwable
invokeExact
のコール・サイトのシンボリック型記述子は、このメソッド・ハンドルの型
と厳密に一致する必要があります。引数や戻り値の変換は許可されません。
Core Reflection API経由で監視した場合、このメソッドは、1つのオブジェクト配列を取って1つのオブジェクトを返す単一のネイティブ・メソッドのように見えます。このネイティブ・メソッドが、java.lang.reflect.Method.invoke
経由で直接的に呼び出されたり、JNI経由で呼び出されたり、あるいはLookup.unreflect
経由で間接的に呼び出されたりすると、メソッドからUnsupportedOperationException
がスローされます。
args
- 可変引数で静的に表現された、シグネチャ・ポリモーフィズム・パラメータ・リストObject
で静的に表現された、シグネチャ・ポリモーフィズム結果WrongMethodTypeException
- ターゲットの型が呼出し元のシンボリック型記述子と同一でない場合Throwable
- 配下のメソッドからスローされたものがすべて、そのまま変更されずにメソッド・ハンドル呼出し経由で伝播されるpublic final Object invoke(Object... args) throws Throwable
コール・サイトのシンボリック型記述子がこのメソッド・ハンドルの型
と厳密に一致する場合、invokeExact
の場合と同様に呼出しが進みます。
それ以外の場合の呼出しの進行は、まずこのメソッド・ハンドルを調整するためにasType
を呼び出してこのメソッド・ハンドルを必要な型に調整し、その後、調整済みのメソッド・ハンドルでinvokeExact
を使用した場合と同様になります。
asType
呼出しが実際に行われるという保証はありません。JVMが呼出し結果を予測できる場合、JVMは呼出し元の引数に対して直接調整を実行し、ターゲット・メソッド・ハンドルをその厳密な型に従って呼び出す可能性があります。
invoke
のコール・サイトの解決済み型記述子は、レシーバのasType
メソッドの有効な引数でなければいけません。特に、呼出し先が可変引数コレクタでない場合、呼出し元は呼出し先の型と同じ引数長を指定する必要があります。
Core Reflection API経由で監視した場合、このメソッドは、1つのオブジェクト配列を取って1つのオブジェクトを返す単一のネイティブ・メソッドのように見えます。このネイティブ・メソッドが、java.lang.reflect.Method.invoke
経由で直接的に呼び出されたり、JNI経由で呼び出されたり、あるいはLookup.unreflect
経由で間接的に呼び出されたりすると、メソッドからUnsupportedOperationException
がスローされます。
args
- 可変引数で静的に表現された、シグネチャ・ポリモーフィズム・パラメータ・リストObject
で静的に表現された、シグネチャ・ポリモーフィズム結果WrongMethodTypeException
- ターゲットの型を呼出し元のシンボリック型記述子に適合させることができない場合ClassCastException
- ターゲットの型を呼出し元に適合させることは可能であるが、参照キャストが失敗した場合Throwable
- 配下のメソッドからスローされたものがすべて、そのまま変更されずにメソッド・ハンドル呼出し経由で伝播されるpublic Object invokeWithArguments(Object... arguments) throws Throwable
Object
型のみを言及し、引数カウントが引数リストの長さであるコール・サイトから、厳密でないinvoke
を使用するのと同じです。
具体的には次の手順のように実行が進みますが、JVMがメソッド呼出しの効果を予測できる場合はメソッドが呼び出される保証はありません。
N
)を決定します。null参照の場合、N=0
になります。 N
個の引数を持つ汎用型TN
を決定します(TN=MethodType.genericMethodType(N)
)。MH0
を必要な型に強制的に変更します(MH1 = MH0.asType(TN)
)。 N
個の個別の引数A0, ...
に分配します。 Object
参照として取得します。
asType
の段階のアクションのために、次の引数変換が必要に応じて適用されます。
呼び出しから返された結果は、プリミティブの場合はボクシングされ、戻り値の型がvoidの場合は強制的にnullにされます。
この呼出しは次のコードと等価です。
MethodHandle invoker = MethodHandles.spreadInvoker(this.type(), 0); Object result = invoker.invokeExact(this, arguments);
invokeWithArguments
はシグネチャ・ポリモーフィズム・メソッドのinvokeExact
やinvoke
とは異なり、Core Reflection APIやJNI経由で通常どおりにアクセスできます。したがってこれは、ネイティブ・コードやリフレクション・コードとメソッド・ハンドルとのブリッジとして使用できます。
arguments
- ターゲットに渡す引数ClassCastException
- 引数が参照キャストで変換できない場合WrongMethodTypeException
- 指定された数のObject
引数を取るようにターゲットの型を調整できない場合Throwable
- ターゲット・メソッド呼出しでスローされたすべてのオブジェクトMethodHandles.spreadInvoker(java.lang.invoke.MethodType, int)
public Object invokeWithArguments(List<?> arguments) throws Throwable
Object
型のみについて言及し、引数の配列の長さに等しい引数長を持つようなコール・サイトから、厳密でないinvoke
を実行するのと同じです。
このメソッドは次のコードとも同等です。
invokeWithArguments(arguments.toArray()
arguments
- ターゲットに渡す引数NullPointerException
- arguments
がnull参照の場合ClassCastException
- 引数が参照キャストで変換できない場合WrongMethodTypeException
- 指定された数のObject
引数を取るようにターゲットの型を調整できない場合Throwable
- ターゲット・メソッド呼出しでスローされたすべてのオブジェクトpublic MethodHandle asType(MethodType newType)
元の型と新しい型が等しい場合はthis
を返します。
新しいメソッド・ハンドルを呼び出すと、次の手順が実行されます。
このメソッドのために、invokeExact
と厳密でないプレーンなinvoke
との間で動作が大きく異なります。この2つのメソッドは、呼出し元と呼出し先の型記述子が厳密に一致する場合は同じ手順を実行しますが、両者の型が異なる場合は、プレーンなinvoke
ではasType
(またはそれに相当する何らかの内部機能)の呼び出しも行われ、呼出し元の型と呼出し先の型が対応付けられます。
現在のメソッドが可変引数メソッド・ハンドルの場合、別の場所で説明しているように、引数リストの変換時にいくつかの引数の1つの配列内への変換と収集が行われる可能性があります。その他のすべての場合には、あらゆる変換はペアで適用され、それぞれの引数または戻り値は厳密に1つの引数または戻り値(または戻り値なし)に変換されます。適用する変換を定義する際には、古いメソッド・ハンドルと新しいメソッド・ハンドルの型に含まれる対応するコンポーネントの型が参照されます。
T0とT1を、対応する新しいパラメータの型と古いパラメータの型、あるいは古い戻り値の型と新しい戻り値の型とします。具体的には、ある有効なインデックスi
について、T0=newType.parameterType(i)
、T1=this.type().parameterType(i)
とします。また、戻り値については、T0=this.type().returnType()
、T1=newType.returnType()
とします。型が同じである場合、新しいメソッド・ハンドルは、対応する引数または戻り値(存在する場合)に対して何の変更も加えません。それ以外の場合、可能であれば次のいずれかの変換が適用されます。
java.lang.reflect.Method.invoke
によって許可される変換になります。)アンボクシング変換は成功する可能性が存在する必要があり、T0自体がラッパー・クラスでない場合は、T0のサブタイプの中に、アンボクシング後のプリミティブ値がT1にワイドニング可能であるようなラッパー・クラスTWが少なくとも1つ存在する必要があります。
必要なペア単位の変換のいずれかを行えない場合は、メソッド・ハンドルの変換を行えません。
参照の引数や戻り値に適用される変換では、実行時に、失敗する可能性のある追加の実行時チェックが必要になる可能性があります。アンボクシング操作は、元の参照がnullであるために失敗する可能性があり、NullPointerException
が発行されます。不正な型のオブジェクトへの参照の場合にもアンボクシング操作や参照キャストが失敗する可能性があり、ClassCastException
が発行されます。アンボクシング操作は何種類かのラッパーを受け入れることができますが、使用可能なものが1つもなければ、ClassCastException
がスローされます。
newType
- 新しいメソッド・ハンドルの期待される型this
に委譲し、必要なあらゆる戻り値変換の手配も行うメソッド・ハンドルNullPointerException
- newType
がnull参照の場合WrongMethodTypeException
- 変換できない場合MethodHandles.explicitCastArguments(java.lang.invoke.MethodHandle, java.lang.invoke.MethodType)
public MethodHandle asSpreader(Class<?> arrayType, int arrayLength)
arrayLength
個のパラメータが単一のarrayType
型の配列パラメータで置き換えられる点が異なります。
配列要素の型が元のターゲットの対応するいずれかの引数の型と異なる場合、asType
が呼び出されたかのようにして、元のターゲットが配列要素を直接取るように適応されます。
このアダプタを呼び出すと、末尾の配列引数がその配列の各要素で置き換えられ、各要素がそれぞれターゲットに対する独立した引数になります。(引数の順序は維持されます。)それらは、ターゲットの末尾の各パラメータの型へのキャストやアンボクシングが行われて、ペアで変換されます。最後にターゲットが呼び出されます。ターゲットから最終的に返されたものが、そのまま変更されずにアダプタから返されます。
アダプタはターゲットを呼び出す前に、配列にちょうど十分な数の要素が含まれていて、ターゲット・メソッド・ハンドルに正しい数の引数を提供できることを確認します。(必要な要素の数がゼロの場合は、配列をnullにしてもかまいません。)
アダプタが呼び出されるときに、渡される配列引数が正しい数の要素を持たない場合、アダプタはターゲットを呼び出すかわりにIllegalArgumentException
をスローします。
配列分配メソッド・ハンドルの単純な例を、いくつか次に示します。
MethodHandle equals = publicLookup() .findVirtual(String.class, "equals", methodType(boolean.class, Object.class)); assert( (boolean) equals.invokeExact("me", (Object)"me")); assert(!(boolean) equals.invokeExact("me", (Object)"thee")); // spread both arguments from a 2-array: MethodHandle eq2 = equals.asSpreader(Object[].class, 2); assert( (boolean) eq2.invokeExact(new Object[]{ "me", "me" })); assert(!(boolean) eq2.invokeExact(new Object[]{ "me", "thee" })); // try to spread from anything but a 2-array: for (int n = 0; n <= 10; n++) { Object[] badArityArgs = (n == 2 ? null : new Object[n]); try { assert((boolean) eq2.invokeExact(badArityArgs) && false); } catch (IllegalArgumentException ex) { } // OK } // spread both arguments from a String array: MethodHandle eq2s = equals.asSpreader(String[].class, 2); assert( (boolean) eq2s.invokeExact(new String[]{ "me", "me" })); assert(!(boolean) eq2s.invokeExact(new String[]{ "me", "thee" })); // spread second arguments from a 1-array: MethodHandle eq1 = equals.asSpreader(Object[].class, 1); assert( (boolean) eq1.invokeExact("me", new Object[]{ "me" })); assert(!(boolean) eq1.invokeExact("me", new Object[]{ "thee" })); // spread no arguments from a 0-array or null: MethodHandle eq0 = equals.asSpreader(Object[].class, 0); assert( (boolean) eq0.invokeExact("me", (Object)"me", new Object[0])); assert(!(boolean) eq0.invokeExact("me", (Object)"thee", (Object[])null)); // asSpreader and asCollector are approximate inverses: for (int n = 0; n <= 2; n++) { for (Class<?> a : new Class<?>[]{Object[].class, String[].class, CharSequence[].class}) { MethodHandle equals2 = equals.asSpreader(a, n).asCollector(a, n); assert( (boolean) equals2.invokeWithArguments("me", "me")); assert(!(boolean) equals2.invokeWithArguments("me", "thee")); } } MethodHandle caToString = publicLookup() .findStatic(Arrays.class, "toString", methodType(String.class, char[].class)); assertEquals("[A, B, C]", (String) caToString.invokeExact("ABC".toCharArray())); MethodHandle caString3 = caToString.asCollector(char[].class, 3); assertEquals("[A, B, C]", (String) caString3.invokeExact('A', 'B', 'C')); MethodHandle caToString2 = caString3.asSpreader(char[].class, 2); assertEquals("[A, B, C]", (String) caToString2.invokeExact('A', "BC".toCharArray()));
arrayType
- 通常はObject[]
で、分配する引数の抽出元となる配列引数の型arrayLength
- 入力配列引数から分配する引数の数NullPointerException
- arrayType
がnull参照の場合IllegalArgumentException
- arrayType
が配列型でない場合、ターゲットが少なくともarrayLength
パラメータ型を持たない場合、arrayLength
が負の場合、または結果のメソッド・ハンドルの型のパラメータ数が多すぎる場合WrongMethodTypeException
- 暗黙的なasType
呼出しが失敗した場合asCollector(java.lang.Class<?>, int)
public MethodHandle asCollector(Class<?> arrayType, int arrayLength)
arrayType
型)がarrayLength
個のパラメータ(型はarrayType
の要素の型)で置き換えられる点が異なります。
配列型が元のターゲットの最後の引数の型と異なる場合、asType
が呼び出されたかのようにして、元のターゲットがその配列型を直接取るように適応されます。
このアダプタを呼び出すと、末尾のarrayLength
個の引数が単一の新しいarrayType
型配列で置き換えられますが、その配列の要素は、置き換えられた引数を(順番に)並べたものになります。最後にターゲットが呼び出されます。ターゲットから最終的に返されたものが、そのまま変更されずにアダプタから返されます。
(arrayLength
がゼロの場合は、配列が共有定数になる可能性もあります。)
(注: arrayType
は通常は、元のターゲットの最後のパラメータの型と同じになります。これが明示的な引数になっているのは、asSpreader
との対称性を維持するためですが、ターゲットの最後のパラメータの型として単純なObject
を使用できるようにするためでもあります。)
収集対象引数の特定の数に制限されない収集アダプタを作成するには、代わりにasVarargsCollector
を使用してください。
配列収集メソッド・ハンドルの例を、いくつか次に示します。
MethodHandle deepToString = publicLookup() .findStatic(Arrays.class, "deepToString", methodType(String.class, Object[].class)); assertEquals("[won]", (String) deepToString.invokeExact(new Object[]{"won"})); MethodHandle ts1 = deepToString.asCollector(Object[].class, 1); assertEquals(methodType(String.class, Object.class), ts1.type()); //assertEquals("[won]", (String) ts1.invokeExact( new Object[]{"won"})); //FAIL assertEquals("[[won]]", (String) ts1.invokeExact((Object) new Object[]{"won"})); // arrayType can be a subtype of Object[] MethodHandle ts2 = deepToString.asCollector(String[].class, 2); assertEquals(methodType(String.class, String.class, String.class), ts2.type()); assertEquals("[two, too]", (String) ts2.invokeExact("two", "too")); MethodHandle ts0 = deepToString.asCollector(Object[].class, 0); assertEquals("[]", (String) ts0.invokeExact()); // collectors can be nested, Lisp-style MethodHandle ts22 = deepToString.asCollector(Object[].class, 3).asCollector(String[].class, 2); assertEquals("[A, B, [C, D]]", ((String) ts22.invokeExact((Object)'A', (Object)"B", "C", "D"))); // arrayType can be any primitive array type MethodHandle bytesToString = publicLookup() .findStatic(Arrays.class, "toString", methodType(String.class, byte[].class)) .asCollector(byte[].class, 3); assertEquals("[1, 2, 3]", (String) bytesToString.invokeExact((byte)1, (byte)2, (byte)3)); MethodHandle longsToString = publicLookup() .findStatic(Arrays.class, "toString", methodType(String.class, long[].class)) .asCollector(long[].class, 1); assertEquals("[123]", (String) longsToString.invokeExact((long)123));
arrayType
- 通常はObject[]
で、複数の引数を集める配列引数の型arrayLength
- 新しい配列引数内に集める引数の数NullPointerException
- arrayType
がnull参照の場合IllegalArgumentException
- arrayType
が配列型でない、またはarrayType
がこのメソッド・ハンドルの末尾のパラメータ型に代入できない、またはarrayLength
が正しい配列サイズでない、または結果のメソッド・ハンドルの型のパラメータ数が多すぎる場合WrongMethodTypeException
- 暗黙的なasType
呼出しが失敗した場合asSpreader(java.lang.Class<?>, int)
, asVarargsCollector(java.lang.Class<?>)
public MethodHandle asVarargsCollector(Class<?> arrayType)
アダプタの型や動作はターゲットの型や動作と基本的に同じになりますが、特定のinvoke
要求やasType
要求によって、末尾の複数の定位置引数がターゲットの末尾のパラメータ内に集められる可能性がある点が異なります。さらに、アダプタの最後のパラメータの型はarrayType
になりますが、ターゲットの最後のパラメータの型がそれと異なる場合でもそうなります。
メソッド・ハンドルがすでに可変引数であり、その末尾のパラメータの型がarrayType
と同じである場合、この変換によってthis
が返される可能性があります。
invokeExact
でアダプタを呼び出した場合、ターゲットの呼出し時に引数は一切変更されません。(注: この動作は固定引数コレクタとは異なりますが、それは、これが固定長の引数ではなく不定長の配列全体を受け入れるからです。)
厳密でないプレーンなinvoke
でアダプタを呼び出したときに呼出し元の型がアダプタと同じ場合、invokeExact
の場合と同様にターゲットが呼び出されます。(これは、型が一致する場合のinvoke
の通常の動作です。)
それ以外の場合、呼出し元とアダプタの引数長が同じで、呼出し元の末尾のパラメータの型がアダプタの末尾のパラメータの型と同じかその型に代入可能な参照型である場合、固定引数のメソッド・ハンドルでasType
を使用する場合と同様に、引数と戻り値がペアで変換されます。
それ以外の場合は、引数長が異なるか、アダプタの末尾のパラメータの型が、呼出し元の対応する型から代入可能ではありません。この場合、アダプタは、元の末尾の引数位置から先にあるすべての末尾の引数を新しい1つのarrayType
型配列で置き換えますが、その配列の要素は、置き換えられた引数を(順番に)並べたものになります。
呼出し元の型は少なくとも、末尾の配列引数より前にある定位置引数に関するターゲットの要件を満たせるように、十分な数の正しい型の引数を提供する必要があります。したがって、呼出し元は最低限、N-1
個の引数(N
はターゲットの引数長)を提供する必要があります。さらに、入力引数からターゲットの引数への変換が存在する必要があります。プレーンなinvoke
のその他の使用法と同じく、これらの基本的な要件が満たされない場合はWrongMethodTypeException
がスローされる可能性があります。
すべての場合で、ターゲットから最終的に返されたものが、そのまま変更されずにアダプタから返されます。
最後の場合は、ターゲット・メソッド・ハンドルが一時的に、呼出し元の型で要求される引数長に固定引数コレクタで適応される場合とまったく同じです。(asCollector
の場合と同様に、配列の長さがゼロの場合は、新しい配列の代わりに共有定数が使用される可能性があります。暗黙的なasCollector
呼出しでIllegalArgumentException
またはWrongMethodTypeException
がスローされた場合、可変引数アダプタの呼び出しからWrongMethodTypeException
がスローされる必要があります。)
また、asType
の動作も可変引数アダプタ向けに特殊化されており、その結果、厳密でないプレーンなinvoke
が常に、asType
を呼び出してターゲットの型を調整したあとでinvokeExact
を呼び出すのと同等であるという不変性が維持されています。したがって、可変引数アダプタがasType
要求への応答として固定引数コレクタを構築するのは、アダプタと要求された型の、引数長または末尾の引数の型が異なる場合だけです。結果となる固定引数コレクタの型は、(必要であれば) asType
がふたたび適用されたかのようにして、ペア単位の変換によって、要求された型にさらに調整されます。
メソッド・ハンドルがCONSTANT_MethodHandle
定数のldc
命令を実行して取得されたものであり、そのターゲット・メソッドが(修飾子ビット0x0080
で)可変引数メソッドとしてマークされている場合、そのメソッド・ハンドル定数がasVarargsCollector
を呼び出して作成されたかのように、そのメソッド・ハンドルは複数の引数長を受け入れます。
事前に決められた数の引数を収集し、その数を反映した型を持つ収集アダプタを作成するには、代わりにasCollector
を使用してください。
どのメソッド・ハンドル変換も、ドキュメントに明記されていないかぎり、可変引数の新しいメソッド・ハンドルを生成することはありません。したがって、asVarargsCollector
を除いて、MethodHandle
とMethodHandles
のすべてのメソッドは固定引数のメソッド・ハンドルを返します(ただし、メソッド・ハンドル自身の型のasType
のように、元のオペランドを返すように指定されている場合は除きます)。
すでに可変引数であるメソッド・ハンドル上でasVarargsCollector
を呼び出すと、同じ型と動作を備えたメソッド・ハンドルが生成されます。その戻り値は、元の可変引数メソッド・ハンドルであることも、そうでないこともあります。
リストを作成する可変引数メソッド・ハンドルの例を、次に示します。
MethodHandle deepToString = publicLookup() .findStatic(Arrays.class, "deepToString", methodType(String.class, Object[].class)); MethodHandle ts1 = deepToString.asVarargsCollector(Object[].class); assertEquals("[won]", (String) ts1.invokeExact( new Object[]{"won"})); assertEquals("[won]", (String) ts1.invoke( new Object[]{"won"})); assertEquals("[won]", (String) ts1.invoke( "won" )); assertEquals("[[won]]", (String) ts1.invoke((Object) new Object[]{"won"})); // findStatic of Arrays.asList(...) produces a variable arity method handle: MethodHandle asList = publicLookup() .findStatic(Arrays.class, "asList", methodType(List.class, Object[].class)); assertEquals(methodType(List.class, Object[].class), asList.type()); assert(asList.isVarargsCollector()); assertEquals("[]", asList.invoke().toString()); assertEquals("[1]", asList.invoke(1).toString()); assertEquals("[two, too]", asList.invoke("two", "too").toString()); String[] argv = { "three", "thee", "tee" }; assertEquals("[three, thee, tee]", asList.invoke(argv).toString()); assertEquals("[three, thee, tee]", asList.invoke((Object[])argv).toString()); List ls = (List) asList.invoke((Object)argv); assertEquals(1, ls.size()); assertEquals("[three, thee, tee]", Arrays.toString((Object[])ls.get(0)));
解説: これらのルールは、可変引数メソッド用のJavaルールの動的型付けバリエーションとして設計されたものです。どちらの場合も、可変引数メソッドまたはメソッド・ハンドルの呼出し元は、ゼロ以上の定位置引数を渡すことも、事前に収集された任意の長さの配列を渡すこともできます。ユーザーは最後の引数の特殊な役割とその最後の引数での型一致の効果を認識すべきであり、その型一致によって、末尾の単一の引数が配列全体として解釈されるか、それとも収集対象となる配列の単一要素として解釈されるかが決まります。末尾の引数の動的な型はこの決定には何の効果も持たず、効果を持つのは、コール・サイトのシンボリック型記述子とメソッド・ハンドルの型記述子の比較だけです。)
arrayType
- 通常はObject[]
で、複数の引数を集める配列引数の型NullPointerException
- arrayType
がnull参照の場合IllegalArgumentException
- arrayType
が配列型でないか、arrayType
をこのメソッド・ハンドルの末尾のパラメータの型に代入できない場合asCollector(java.lang.Class<?>, int)
, isVarargsCollector()
, asFixedArity()
public boolean isVarargsCollector()
CONSTANT_MethodHandle
のldc
命令
invoke
呼出しの複数の引数長を受け入れる場合はtrueasVarargsCollector(java.lang.Class<?>)
, asFixedArity()
public MethodHandle asFixedArity()
現在のメソッド・ハンドルが可変引数でない場合、現在のメソッド・ハンドルが返されます。これは、現在のメソッド・ハンドルをasVarargsCollector
の有効な入力として指定できない場合でも言えることです。
それ以外の場合、結果となる固定引数メソッド・ハンドルは現在のメソッド・ハンドルと基本的に同じ型と動作を備えていますが、isVarargsCollector
がfalseである点だけは異なります。固定引数メソッド・ハンドルは、asVarargsCollector
への以前の引数であることも、そうでないこともあります。
リストを作成する可変引数メソッド・ハンドルの例を、次に示します。
MethodHandle asListVar = publicLookup() .findStatic(Arrays.class, "asList", methodType(List.class, Object[].class)) .asVarargsCollector(Object[].class); MethodHandle asListFix = asListVar.asFixedArity(); assertEquals("[1]", asListVar.invoke(1).toString()); Exception caught = null; try { asListFix.invoke((Object)1); } catch (Exception ex) { caught = ex; } assert(caught instanceof ClassCastException); assertEquals("[two, too]", asListVar.invoke("two", "too").toString()); try { asListFix.invoke("two", "too"); } catch (Exception ex) { caught = ex; } assert(caught instanceof WrongMethodTypeException); Object[] argv = { "three", "thee", "tee" }; assertEquals("[three, thee, tee]", asListVar.invoke(argv).toString()); assertEquals("[three, thee, tee]", asListFix.invoke(argv).toString()); assertEquals(1, ((List) asListVar.invoke((Object)argv)).size()); assertEquals("[three, thee, tee]", asListFix.invoke((Object)argv).toString());
asVarargsCollector(java.lang.Class<?>)
, isVarargsCollector()
public MethodHandle bindTo(Object x)
x
をメソッド・ハンドルの最初の引数にバインドしますが、その呼出しは行いません。新しいメソッド・ハンドルは、そのターゲットである現在のメソッド・ハンドルを適応させるため、指定された引数にターゲットをバインドします。バインドされたハンドルの型はターゲットの型と基本的に同じになりますが、先頭の単一の参照パラメータが省略される点が異なります。
バインドされたハンドルを呼び出すと、指定された値x
が、ターゲットの新しい先頭の引数として挿入されます。その他の引数も変更されずに渡されます。ターゲットから最終的に返されたものが、そのまま変更されずにバインドされたハンドルから返されます。
参照x
は、ターゲットの最初のパラメータの型に変換できなければいけません。
(注: メソッド・ハンドルは不変であるため、ターゲット・メソッド・ハンドルはその元の型と動作を保持します。)
x
- ターゲットの最初の引数にバインドする値IllegalArgumentException
- ターゲットの先頭のパラメータの型が参照型でない場合ClassCastException
- x
をターゲットの先頭のパラメータの型に変換できない場合MethodHandles.insertArguments(java.lang.invoke.MethodHandle, int, java.lang.Object...)
public String toString()
"MethodHandle"
で始まり、メソッド・ハンドルの型の文字列表現で終わります。つまり、このメソッドは次の値と等しい文字列を返します。
"MethodHandle" + type().toString()
(注: このAPIの将来のリリースでは、さらなる情報が文字列表現に追加される可能性があります。したがって、現在の構文をアプリケーション内で解析しないようにしてください。)
バグまたは機能を送信
詳細なAPIリファレンスおよび開発者ドキュメントについては、Java SEのドキュメントを参照してください。そのドキュメントには、概念的な概要、用語の定義、回避方法、有効なコード例などの、開発者を対象にしたより詳細な説明が含まれています。
Copyright© 1993, 2014, Oracle and/or its affiliates. All rights reserved.