1. 序章

この記事では、Java 7で導入され、次のバージョンで拡張された重要なAPI java.lang.invoke.MethodHandlesについて説明します。

特に、メソッドハンドルとは何か、それらを作成する方法、およびそれらを使用する方法を学習します。

2. メソッドハンドルとは何ですか?

APIドキュメントに記載されているように、その定義について説明します。

メソッドハンドルは、基になるメソッド、コンストラクター、フィールド、または同様の低レベルの操作への型指定された直接実行可能な参照であり、オプションで引数または戻り値の変換が行われます。

簡単に言うと、メソッドハンドルは、メソッドを検索、適合、および呼び出すための低レベルのメカニズムです。

メソッドハンドルは不変であり、目に見える状態はありません。

MethodHandle を作成して使用するには、次の4つの手順が必要です。

  • ルックアップの作成
  • メソッドタイプの作成
  • メソッドハンドルを見つける
  • メソッドハンドルの呼び出し

2.1. メソッドハンドルとリフレクション

メソッドハンドルは、既存の java.lang.reflect APIと連携して機能するために導入されました。これは、メソッドハンドルがさまざまな目的を果たし、さまざまな特性を備えているためです。

パフォーマンスの観点から、 MethodHandles APIは、実行時ではなく作成時にアクセスチェックが行われるため、ReflectionAPIよりもはるかに高速です。 メンバーとクラスのルックアップは追加のチェックの対象となるため、セキュリティマネージャーが存在する場合、この違いはさらに大きくなります。

ただし、パフォーマンスだけがタスクの適合性の尺度ではないことを考慮すると、 MethodHandles APIは、メンバークラスの列挙、アクセシビリティフラグの検査などのメカニズムがないため、使いにくいことも考慮する必要があります。もっと。

それでも、 MethodHandles APIは、メソッドをカレーし、パラメーターのタイプを変更し、それらの順序を変更する可能性を提供します。

MethodHandles APIの明確な定義と目標があれば、ルックアップから始めて、それらの操作を開始できます。

3. ルックアップの作成

メソッドハンドルを作成するときに最初に行うことは、ルックアップクラスに表示されるメソッド、コンストラクター、およびフィールドのメソッドハンドルの作成を担当するファクトリオブジェクトであるルックアップを取得することです。

MethodHandles APIを使用すると、さまざまなアクセスモードでルックアップオブジェクトを作成できます。

publicメソッドへのアクセスを提供するルックアップを作成しましょう。

MethodHandles.Lookup publicLookup = MethodHandles.publicLookup();

ただし、privateおよびprotectedメソッドにもアクセスしたい場合は、代わりに lookup()メソッドを使用できます。

MethodHandles.Lookup lookup = MethodHandles.lookup();

4. MethodTypeの作成

MethodHandle を作成できるようにするには、ルックアップオブジェクトにそのタイプの定義が必要であり、これはMethodTypeクラスを介して実現されます。

特に、 a MethodTypeは、メソッドハンドルによって受け入れられて返されるか、メソッドハンドル呼び出し元によって渡されて期待される引数と戻り型を表します。

MethodType の構造は単純であり、メソッドハンドルとそのすべての呼び出し元の間で適切に一致する必要がある適切な数のパラメーター型とともに、戻り型によって形成されます。

MethodHandle と同じように、MethodTypeのインスタンスも不変です。

java .util.List クラスを戻り型として指定し、Object配列を入力型として指定するMethodTypeを定義する方法を見てみましょう。

MethodType mt = MethodType.methodType(List.class, Object[].class);

メソッドがプリミティブ型またはvoidを戻り型として返す場合、それらの型を表すクラス(void.class、int.class…)を使用します。

int値を返し、Objectを受け入れるMethodTypeを定義しましょう。

MethodType mt = MethodType.methodType(int.class, Object.class);

これで、MethodHandleの作成に進むことができます。

5. MethodHandleを見つける

メソッドタイプを定義したら、 MethodHandleを作成するために、lookupまたはpublicLookupオブジェクトを介して検索し、オリジンも提供する必要があります。クラスとメソッド名。

特に、ルックアップファクトリはメソッドのセットを提供します。これにより、メソッドのスコープを考慮して適切な方法でメソッドハンドルを見つけることができます。 最も単純なシナリオから始めて、主要なシナリオを調べてみましょう。

5.1. メソッドのメソッドハンドル

findVirtual()メソッドを使用すると、オブジェクトメソッドのMethodHandleを作成できます。 Stringクラスのconcat()メソッドに基づいて作成しましょう。

MethodType mt = MethodType.methodType(String.class, String.class);
MethodHandle concatMH = publicLookup.findVirtual(String.class, "concat", mt);

5.2. 静的メソッドのメソッドハンドル

静的メソッドにアクセスしたい場合は、代わりに findStatic()メソッドを使用できます。

MethodType mt = MethodType.methodType(List.class, Object[].class);

MethodHandle asListMH = publicLookup.findStatic(Arrays.class, "asList", mt);

この場合、オブジェクトの配列をそれらのリストに変換するメソッドハンドルを作成しました。

5.3. コンストラクターのメソッドハンドル

コンストラクターへのアクセスの取得は、 findConstructor()メソッドを使用して実行できます。

Integer クラスのコンストラクターとして動作し、String属性を受け入れるメソッドハンドルを作成しましょう。

MethodType mt = MethodType.methodType(void.class, String.class);

MethodHandle newIntegerMH = publicLookup.findConstructor(Integer.class, mt);

5.4. フィールドのメソッドハンドル

メソッドハンドルを使用すると、フィールドにもアクセスできます。

Bookクラスの定義を始めましょう。

public class Book {
    
    String id;
    String title;

    // constructor

}

メソッドハンドルと宣言されたプロパティの間の直接アクセスの可視性を前提条件として、ゲッターとして動作するメソッドハンドルを作成できます。

MethodHandle getTitleMH = lookup.findGetter(Book.class, "title", String.class);

変数/フィールドの処理の詳細については、 Java 9 Variable Handles Demystified を参照してください。ここでは、Java9で追加されたjava.lang.invoke.VarHandleAPIについて説明します。

5.5. プライベートメソッドのメソッドハンドル

java.lang.reflect APIを使用して、プライベートメソッドのメソッドハンドルを作成できます。

privateメソッドをBookクラスに追加してみましょう。

private String formatBook() {
    return id + " > " + title;
}

これで、 formatBook()メソッドとまったく同じように動作するメソッドハンドルを作成できます。

Method formatBookMethod = Book.class.getDeclaredMethod("formatBook");
formatBookMethod.setAccessible(true);

MethodHandle formatBookMH = lookup.unreflect(formatBookMethod);

6. メソッドハンドルの呼び出し

メソッドハンドルを作成したら、それを使用して次のステップに進みます。 特に、 MethodHandle クラスは、メソッドハンドルを実行するための3つの異なる方法を提供します。 ]。

invokeオプションから始めましょう。

6.1. メソッドハンドルの呼び出し

invoke()メソッドを使用する場合、引数の数(アリティ)を固定するように強制しますが、引数と戻り型のキャストとボックス化/アンボックス化の実行を許可します。

ボックス化された引数でinvoke()を使用する方法を見てみましょう。

MethodType mt = MethodType.methodType(String.class, char.class, char.class);
MethodHandle replaceMH = publicLookup.findVirtual(String.class, "replace", mt);

String output = (String) replaceMH.invoke("jovo", Character.valueOf('o'), 'a');

assertEquals("java", output);

この場合、replaceMHにはchar引数が必要ですが、 invoke()は、実行前にCharacter引数に対してボックス化解除を実行します。

6.2. 引数を使用した呼び出し

invokeWithArguments メソッドを使用してメソッドハンドルを呼び出すことは、3つのオプションの中で最も制限が少ないものです。

実際、引数と戻り型のキャストとボックス化/ボックス化解除に加えて、可変のアリティ呼び出しを可能にします。

練習に入ると、これにより、int値の配列から始まる整数リストを作成できます。

MethodType mt = MethodType.methodType(List.class, Object[].class);
MethodHandle asList = publicLookup.findStatic(Arrays.class, "asList", mt);

List<Integer> list = (List<Integer>) asList.invokeWithArguments(1,2);

assertThat(Arrays.asList(1,2), is(list));

6.3. 正確な呼び出し

メソッドハンドル(引数の数とそのタイプ)の実行方法をより制限したい場合は、 invokeExact()メソッドを使用する必要があります。

実際、提供されたクラスへのキャストは提供されず、固定数の引数が必要です。

メソッドハンドルを使用して、2つの sum2つのint値をsumする方法を見てみましょう。

MethodType mt = MethodType.methodType(int.class, int.class, int.class);
MethodHandle sumMH = lookup.findStatic(Integer.class, "sum", mt);

int sum = (int) sumMH.invokeExact(1, 11);

assertEquals(12, sum);

この場合、 invokeExactメソッドにint 以外の数値を渡すことにした場合、呼び出しによってWrongMethodTypeException。が発生します。

7. アレイの操作

MethodHandles は、フィールドまたはオブジェクトだけでなく、配列でも機能することを目的としています。 実際のところ、 asSpreader() APIを使用すると、配列拡散メソッドを処理することができます。

この場合、メソッドハンドルは配列引数を受け入れ、その要素を位置引数として、オプションで配列の長さを拡散します。

メソッドハンドルを広げて、配列内の要素が等しいかどうかを確認する方法を見てみましょう。

MethodType mt = MethodType.methodType(boolean.class, Object.class);
MethodHandle equals = publicLookup.findVirtual(String.class, "equals", mt);

MethodHandle methodHandle = equals.asSpreader(Object[].class, 2);

assertTrue((boolean) methodHandle.invoke(new Object[] { "java", "java" }));

8. メソッドハンドルの強化

メソッドハンドルを定義したら、実際に呼び出さずにメソッドハンドルを引数にバインドすることで、メソッドハンドルを拡張できます。

たとえば、Java 9では、この種の動作を使用してString連結を最適化します。

concatMH にサフィックスをバインドして、連結を実行する方法を見てみましょう。

MethodType mt = MethodType.methodType(String.class, String.class);
MethodHandle concatMH = publicLookup.findVirtual(String.class, "concat", mt);

MethodHandle bindedConcatMH = concatMH.bindTo("Hello ");

assertEquals("Hello World!", bindedConcatMH.invoke("World!"));

9. Java9の機能強化

Java 9では、使いやすさを大幅に向上させることを目的として、 MethodHandlesAPIにいくつかの機能拡張が行われました。

機能強化は、3つの主要なトピックに影響を与えました。

  • ルックアップ関数–さまざまなコンテキストからのクラスルックアップを可能にし、インターフェイスで非抽象メソッドをサポートします
  • 引数処理–引数の折りたたみ、引数の収集、および引数の拡散機能を改善します
  • 追加の組み合わせ–ループの追加( loop whileLoop、 doWhileLoop…)およびtryFinally[によるより優れた例外処理サポートX170X]

これらの変更により、いくつかの追加の利点がもたらされました。

  • JVMコンパイラの最適化の向上
  • インスタンス化の削減
  • MethodHandlesAPIの使用で精度を有効にしました

行われた拡張機能の詳細は、 MethodHandles APIJavadocで入手できます。

10. 結論

この記事では、 MethodHandles APIとは何か、およびそれらの使用方法について説明しました。

また、Reflection APIとの関連についても説明しました。メソッドハンドルは低レベルの操作を許可するため、ジョブの範囲に完全に適合しない限り、それらの使用は避ける方がよいでしょう。

いつものように、この記事の完全なソースコードは、Githubから入手できます。