Javaのメソッドハンドル

1前書き

この記事では、Java 7で導入され、次のバージョンの httpsで拡張された重要なAPIを探ります。/MethodHandles.html[java.lang.invoke.MethodHandles]

特に、メソッドハンドルとは何か、それらをどのように作成するか、そしてそれらをどのように使用するかを学びます。

** 2メソッドハンドルとは

APIドキュメントに記載されているように、その定義に来る。

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

より簡単な方法では、 メソッドハンドルは メソッドを見つけ、適応させそして呼び出すための低レベルのメカニズムです。

メソッドハンドルは不変であり、可視状態はありません。

MethodHandle を作成して使用するには、4つのステップが必要です。

  • ルックアップを作成する

  • メソッドタイプを作成する

  • メソッドハンドルを見つける

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

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

既存の java.lang.reflect AP​​Iとともに機能するために、メソッドハンドルが導入されましたそれらは異なる目的を果たし、異なる特性を持つからです。

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

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

それでも、 MethodHandles APIはメソッドのカレー化、パラメータの種類の変更、およびそれらの順序の変更を可能にします。

MethodHandles APIの明確な定義と目標を持っているので、ルックアップから始めて、それらを使い始めることができます。

=== 3 Lookup を作成する

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

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__オブジェクトを使用して検索し、元のクラスとメソッド名も指定する必要があります。

特に、ルックアップファクトリはhttps://docs.oracle.com/javase/9​​/docs/api/java/lang/invoke/MethodHandles.Lookup.html#lookups[一連のメソッド]を提供しています。メソッドの範囲を考慮して、適切な方法でメソッドを処理します。最も単純なシナリオから始めて、主要なシナリオを探りましょう。

==== 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);

この場合、 Objects の配列をそれらの List に変換するメソッドハンドルを作成しました。

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

コンストラクタへのアクセスを取得するには、 findConstructor() メソッドを使用します。

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

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);

変数/フィールドの処理の詳細については、https://docs.oracle.com/javase/9​​/docs/を参照してください。 api/java/lang/invoke/VarHandle.html[java.lang.invoke.VarHandle]API。Java9で追加されました。

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

__https://docs.oracle.com/javase/9​​/docs/api/java/lang/reflect/package-summary.html[java.lang]を使用すると、プライベートメソッドのメソッドハンドルを作成できます。 API。

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

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()、 invokeWithArugments() 、および invokeExact()

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 値の array から始まる List of Integer を作成することを可能にします。

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. Exactを起動する

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

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

メソッドハンドルを使用して2つの 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 Java 9の機能強化

Java 9では、使いやすくすることを目的とした MethodHandles APIの拡張はほとんど行われていません。

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

  • ルックアップ関数 - 異なるコンテキストからのクラスルックアップを可能にします

インタフェースで非抽象メソッドをサポートする 引数処理** - 引数の折りたたみ、引数の改善

機能性の収集と議論 追加の組み合わせ** - ループの追加( loop whileLoop、

doWhileLoop …​ )および tryFinally を使用した優れた例外処理サポート

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

  • JVMコンパイラの最適化の向上

  • インスタンス化の削減

MethodHandles APIの使用における精度の向上

行われた機能強化の詳細はhttps://docs.oracle.com/javase/9​​/docs/api/java/lang/invoke/MethodHandles.html[ MethodHandles API Javadoc]にあります。

=== 10結論

この記事では、 MethodHandles API、それらの機能、そしてそれらの使用方法について説明しました。

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

いつものように、この記事の完全なソースコードはhttps://github.com/eugenp/tutorials/tree/master/core-java-9[Githubに掲載]で入手可能です。