メソッド参照をJavaのメソッドに変換する

オブジェクトFunction<A, B> foo = SomeClass::someMethod;があると想像してFunction<A, B> foo = SomeClass::someMethod; これはラムダであり、非静的メソッドへの参照であることが保証されています。 書かれたメソッドに対応するfooオブジェクトからMethodクラスのインスタンスを取得するにはどうすればよいですか?


要するに、何らかの方法で、特定のメソッドに関する情報は排他的にバイトコードに格納されます(そこではあらゆる種類のインストルメンテーションを考慮していません)。 しかし、これは、特定の場合に必要なものを取得することを妨げません。



だから私たちの目標は方法です:


 static <A, B> Method unreference(Function<A, B> foo) { //... } 

次のように使用できます。


 Method m = unreference(SomeClass::someMethod) 

最初に行うことは、メソッドが属するクラスを直接検索することです。 つまり、タイプFunction<A, B>場合、特定のAを見つける必要がありますA 通常、パラメーター化された型とその特定の実装がある場合、特定の実装でgetGenericSuperClass()を呼び出すことでパラメーター型の値を見つけることができます。 良いことには、このメソッドはParameterizedTypeクラスのインスタンスを返す必要があります。これは、 getActualTypeArguments()呼び出しを通じて特定の型の配列によって既に提供されています。


 Type genericSuperclass = foo.getClass().getGenericSuperclass(); Class actualClass = (Class) ((ParameterizedType) genericSuperclass).getActualTypeArguments()[0]; 

しかし、このトリックはラムダでは機能しません-単純さと効率のために、ランタイムはこれらの詳細を詰まらせ、実際にFunction<Object, Object>型のオブジェクトを提供します(この状況では、ブリッジメソッドとあらゆる種類のメタデータを生成する必要はありません)。 そのためのGenericSuperclassはスーパークラスにのみ一致し、上記のコードは機能しません。


これは確かに私たちにとって最高のニュースではありませんが、すべてが失われるわけではありません。ラムダのapply(または他の「機能」メソッド)の実装は次のように見えるためです。


 public Object apply(Object param) { SomeClass cast = (SomeClass) param; return invokeSomeMethod(cast); } 

実際に型情報を格納する数少ない場所の1つであるため、このキャストが必要です(2番目の場所は次の行で呼び出されるメソッドです)。 間違ったクラスのオブジェクトを渡して適用するとどうなりますか? True、 ClassCastException


 try { foo.apply(new Object()); } catch (ClassCastException e) { //... } 

例外のメッセージは次のようになりますjava.lang.Object cannot be cast to sample.SomeClass (少なくともテスト済みのJREのバージョンでは、このメッセージのトピックに関する仕様には何も記載されていません)。 これがObjectクラスのメソッドでない限り、ここでは何も保証できません。


クラスの名前を知っていれば、 Classクラスの対応するインスタンスを取得することは難しくありません。 これで、メソッドの名前を取得することができます。 前述のように、メソッドに関する情報はバイトコードのみであるため、これはより複雑です。 メソッドコールを追跡できるSomeClassクラスの独自のインスタンスがある場合、それを適用して適用し、(コールスタックまたは他の何かを渡した後)何が起こったのかを確認できます。


私の頭に浮かぶ最初のものはもちろんjava.lang.reflect.Proxyですが、これは新しく非常に強い制限を導入しjava.lang.reflect.Proxy -SomeClassはそのプロキシを生成できるようにインターフェースでなければなりません。 一方、コードは同時に完全に初歩的であることがわかります。プロキシを作成し、 applyinvokeInvocationHandlerオブジェクトのinvokeメソッド内でready Methodを取得します。


完全なコードは次のようになります。 当然、実際のプロジェクトで使用することはお勧めしません。


 private static final Pattern classNameExtractor = Pattern.compile("cannot be cast to (.*)$"); public static <A, B> Method unreference(Function<A, B> reference) { Function erased = reference; try { erased.apply(new Object()); } catch (ClassCastException cce) { Matcher matcher = classNameExtractor.matcher(cce.getMessage()); if (matcher.find()) { try { Class<?> iface = Class.forName(matcher.group(1)); if (iface.isInterface()) { AtomicReference<Method> resultHolder = new AtomicReference<>(); Object obj = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{iface}, (proxy, method, args) -> { resultHolder.set(method); return null; } ); try { erased.apply(obj); } catch (Throwable ignored) { } return resultHolder.get(); } } catch (ClassNotFoundException ignored) { } } } throw new RuntimeException("Something's wrong"); } 

次のようにして確認できます(Javaは常に型推論に対応するとは限らないため、別の変数が必要です)。


 Function<List, Integer> size = List::size; System.out.println(unreference(size)); 

このコードは、 public abstract int java.util.List.size()を正しく実行して出力します。



Source: https://habr.com/ru/post/J311788/


All Articles