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 つの不一致が存在しているからです。
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
がスローされます。
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
がスローされます。
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 にしてもかまいません。)
配列分配メソッドハンドルの単純な例を、いくつか次に示します。
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" })); // 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
が配列型でない場合IllegalArgumentException
- ターゲットに含まれるパラメータの型の個数が 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, 2013, Oracle and/or its affiliates. All rights reserved.