7

特定のオブジェクトがメソッドの有効なパラメーターであるかどうかをリフレクションを使用して確認するにはどうすればよいですか (パラメーターとオブジェクトがジェネリック型である場合)。

少し背景を理解するために、これは私が達成しようとしていることです:

リフレクティブ メソッド呼び出しで遊んでいるときに、特定の型のパラメーターを持つすべてのメソッドを呼び出すとよいと思いました。isAssignableFrom(Class<?> c)これは、クラス オブジェクトを呼び出すことができるため、生の型に適しています。ただし、ジェネリックをミックスに投入し始めると、ジェネリックはリフレクションの元の設計の一部ではなく、型消去のために突然簡単ではなくなります。

問題はより大きくなりますが、基本的には次のようになります。

理想的なソリューション

理想はコード

import java.lang.reflect.*;
import java.util.*;


public class ReflectionAbuse {

    public static void callMeMaybe(List<Integer> number) {
        System.out.println("You called me!");
    }

    public static void callMeAgain(List<? extends Number> number) {
        System.out.println("You called me again!");
    }

    public static void callMeNot(List<Double> number) {
        System.out.println("What's wrong with you?");
    }

    public static <T> void reflectiveCall(List<T> number){
        for(Method method : ReflectionAbuse.class.getDeclaredMethods()) {
            if(method.getName().startsWith("call")) {
                if(canBeParameterOf(method, number)) {
                    try {
                        method.invoke(null, number);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    public static <T> boolean canBeParameterOf(Method method, List<T> number) {
        // FIXME some checks missing
        return true;
    }

    public static void main(String[] args) {
        reflectiveCall(new ArrayList<Integer>());
    }

}

印刷します

You called me!
You called me again!

これは、どのようにT見えるかに関係なく可能である必要があります (つまり、 などの別のジェネリック型である可能性がありますList<List<Integer>>)。

T実行時にタイプが消去され、不明であるため、明らかにこれは機能しません。

試行 1

最初に作業できるのは次のようなものです。

import java.lang.reflect.*;
import java.util.*;


public class ReflectionAbuse {

            public static void callMeMaybe(ArrayList<Integer> number) {
                System.out.println("You called me!");
            }

            public static void callMeAgain(ArrayList<? extends Number> number) {
                System.out.println("You called me again!");
            }

            public static void callMeNot(ArrayList<Double> number) {
                System.out.println("What's wrong with you?");
            }

    public static <T> void reflectiveCall(List<T> number){
        for(Method method : ReflectionAbuse.class.getDeclaredMethods()) {
            if(method.getName().startsWith("call")) {
                if(canBeParameterOf(method, number)) {
                    try {
                        method.invoke(null, number);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    public static <T> boolean canBeParameterOf(Method method, List<T> number) {
        return method.getGenericParameterTypes()[0].equals(number.getClass().getGenericSuperclass());
    }

    public static void main(String[] args) {
        reflectiveCall(new ArrayList<Integer>(){});
    }

}

印刷のみ

You called me!

ただし、これにはいくつかの追加の注意事項があります。

  • Type直接インスタンスに対してのみ機能し、インターフェースが必要なメソッドを提供しないため、継承階層は考慮されません。あちこちにキャストすると、これを見つけるのに間違いなく役立ちます(私の2回目の試みも参照してください)
  • の引数は、reflectiveCall実際には必要なパラメーターの型のサブクラスである必要があります (匿名の内部クラスを作成する に注意してください) {}new ArrayList<Integer>(){}これは明らかに理想的ではありません。不要なクラス オブジェクトが作成され、エラーが発生しやすくなります。これが、型消去を回避する唯一の方法です。

試行 2

消去のために理想的なソリューションで欠落している型について考えると、理想に非常に近い引数として型を渡すこともできます。

import java.lang.reflect.*;
import java.util.*;


public class ReflectionAbuse {

    public static void callMeMaybe(List<Integer> number) {
        System.out.println("You called me!");
    }

    public static void callMeAgain(List<? extends Number> number) {
        System.out.println("You called me again!");
    }

    public static void callMeNot(List<Double> number) {
        System.out.println("What's wrong with you?");
    }

    public static <T> void reflectiveCall(List<T> number, Class<T> clazz){
        for(Method method : ReflectionAbuse.class.getDeclaredMethods()) {
            if(method.getName().startsWith("call")) {
                Type n = number.getClass().getGenericSuperclass();
                if(canBeParameterOf(method, clazz)) {
                    try {
                        method.invoke(null, number);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    public static <T> boolean canBeParameterOf(Method method, Class<T> clazz) {
        Type type = ((ParameterizedType)method.getGenericParameterTypes()[0]).getActualTypeArguments()[0];
        if (type instanceof WildcardType) {
            return ((Class<?>)(((WildcardType) type).getUpperBounds()[0])).isAssignableFrom(clazz);
        }
        return ((Class<?>)type).isAssignableFrom(clazz);
    }

    public static void main(String[] args) {
        reflectiveCall(new ArrayList<Integer>(), Integer.class);
    }

}

実際に正しい解決策を出力します。しかし、これにはマイナス面がないわけではありません。

  • のユーザーは、reflectiveCall不要で面倒な型パラメーターを渡す必要があります。少なくとも、コンパイル時に正しい呼び出しがチェックされます。
  • 型パラメータ間の継承は完全には考慮されておらず、canBeParameterOf(型付きパラメータなどで) 実装する必要がある多くのケースが残っています。
  • そして最大の問題: 型パラメーターはそれ自体ではジェネリックにできないため、ListofListの をIntegers引数として使用することはできません。

質問

目標にできるだけ近づけるために、別の方法でできることはありますか? 匿名サブクラスを使用するか、型パラメーターを渡すかのどちらかで行き詰まっていますか? とりあえず、コンパイル時の安全性を確保するため、パラメーターを指定することにします。

パラメーターの型を再帰的にチェックするときに注意する必要があることはありますか?

ソリューション 2 でジェネリックを型パラメーターとして許可する可能性はありますか?

実際、学習目的で、ライブラリを使用する代わりに独自のソリューションを展開したいと考えていますが、いくつかの内部の仕組みを見てもかまいません。


明確にするために、たとえば次のことを認識していますが、例をきれいに保つようにしてください。

  • これは、リフレクションなしで解決できる可能性があります (要件の一部を再設計し、インターフェイスや内部クラスなどを使用する場合)。これは学習用です。私が解決したい問題は、実際にはかなり大きいですが、これが要約です。
  • 命名パターンを使用する代わりに、注釈を使用できます。私は実際にそうしていますが、例をできるだけ自己完結型に保ちたいと思います。
  • によって返される配列はgetGenericParameterTypes()空である可能性がありますが、すべてのメソッドに引数があり、これが事前にチェックされていると仮定しましょう。
  • nullが呼び出されたときに失敗する非静的メソッドが存在する可能性があります。ないと仮定します。
  • catch条件はより具体的である可能性があります。
  • 一部のキャストは、より安全にする必要があります。
4

1 に答える 1