Javaのメソッドハンドルとは

1.はじめに



このチュートリアルでは、Java 7で導入され、新しいバージョンで拡張された重要なAPI java.lang.invoke.MethodHandlesについて説明します













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







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



APIドキュメントでは、メソッドハンドルには次の定義があります。







メソッドハンドルは、ベースメソッド、コンストラクター、フィールド、または引数または戻り値の追加の変換を伴う他の低レベル操作への型付きの実行可能な参照です。

言い換えると、メソッドハンドルは、メソッドを検索、適合、および呼び出すための低レベルのメカニズムです。 メソッドハンドルは不変であり、表示状態はありません。







MethodHandle



を作成して使用するには、4つのアクションを実行する必要があります。







  1. 検索記述子を作成する-ルックアップ
  2. メソッドタイプの宣言
  3. 検索方法ハンドル
  4. メソッドハンドルを呼び出す


2.1。 メソッドハンドルと反射



メソッドハンドルは、 java.lang.reflect APIとともに機能するように導入されました。 それらは異なる目的のために作成され、特性が異なります。







パフォーマンスの観点から、アクセスチェックは実行ではなく作成時に実行されるためMethodHandles APIはReflection APIよりもはるかに高速です 。 セキュリティマネージャーがいる場合、この違いは大きくなります。 クラスの検索とその要素の取得には、追加のチェックが必要です。







ただし、パフォーマンスはタスクの最適性の唯一の指標ではありません。クラスメソッドの取得、アクセストークンのチェックなどのメカニズムがないため、MethodHandles APIの使用はより困難であることに注意してください。







それにもかかわらず、MethodHandles APIはメソッドのカリー化、パラメーターのタイプと順序の変更を許可します。







これで、MethodHandles APIの定義と目的がわかったので、それらを操作できます。 メソッドを探すことから始めましょう。







3.ルックアップの作成



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







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







パブリックメソッドへのアクセスを提供するルックアップを作成します。







 MethodHandles.Lookup publicLookup = MethodHandles.publicLookup();
      
      





ただし、privateメソッドとprotectedメソッドにアクセスする必要がある場合は、代わりにlookup()メソッドを使用できます。







 MethodHandles.Lookup lookup = MethodHandles.lookup();
      
      





4. MethodTypeの作成



MethodHandleを作成するには、ルックアップオブジェクトを型に設定する必要があり、これはMethodTypeクラスを使用して実行できます。







特に、 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. SearchMethodHandle



メソッドのタイプを設定した後、MethodHandleを作成するには、ルックアップオブジェクトまたはpublicLookupを使用して検索する必要があります。これは、ソースクラスとメソッド名も返します。







Lookupは、メソッドのスコープを考慮して、最適な方法でメソッドハンドルを見つけることができる一連のメソッドを提供します。 最も単純なものから始めて、基本的なアプローチを検討してください。







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





この場合、 Object



型の配列を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 }
      
      





初期条件として、メソッドハンドルと宣言されたプロパティの間を直接表示できるため、getメソッドと同様の動作を持つメソッドハンドルを作成できます。







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





変数/フィールド管理の詳細については、 Java 9 Variable Handles Demystifiedの記事を参照してください。Java9で導入されたjava.lang.invoke.VarHandle APIについて説明しています







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



invokeWithArugments()



、およびinvokeExact()









invoke



メソッドから始めましょう。







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



invoke()



メソッドを使用する場合、引数(アリティ)の数は固定されますが、引数と戻り値の型の型キャストとパック/アンパックを実行することは可能です。







次に、パックされた引数で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



を使用したメソッドハンドルのinvokeWithArguments



は、最小限の制限があります。







実際、型をチェックし、引数と戻り値をパック/アンパックすることに加えて、可変数のパラメーターで呼び出しを行うことができます。







実際には、長さが不明な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つのint値を追加する方法を見てみましょう。







 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では、このトリックを使用して文字列の連結を最適化します。







接尾辞を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つの主な側面に関係します。









これらの変更には、他の有用な革新が伴いました。









変更のより詳細なリストは、Javadoc MethodHandles APIで入手できます







10.結論



この記事では、MethodHandles APIに出会い、メソッドハンドルとは何か、そしてそれらを使用する方法についても学びました。







また、Reflection APIとの関連付けについても説明しました。 メソッドハンドルの呼び出しはかなり低レベルの操作であるため、それらの使用はタスクに正確に適合する場合にのみ正当化されます。







いつものように、記事のすべてのソースコードはGithubで入手できます








All Articles