10

ジェネリックな戻り値の型を持つメソッドとのインターフェイスがあり、実行時にそのインターフェイスを間接的に実装するクラスのインスタンスがいくつかあります。ここで、リフレクションを使用して各実装の実際の戻り値の型を調べたいと思います。

(私の考えは、このメカニズムを使用して、インターフェイスを使用して戦略を定義し、実行時に一連の戦略実装から一致する戦略 (特定の戻り値の型) を見つけることです。型を公開する冗長なヘルパー メソッドを導入する必要はありません)。

より具体的には、次のシナリオを考えてみましょう。

private interface DAO <I extends Serializable, E> {

    public E getById (I id);
}

private abstract class AbstractDAO <T> implements DAO<Integer, T> {

    @Override
    public T getById (Integer id) {
        // dummy implementation, just for this example
        return null;
    }
}

private class PersonDAO extends AbstractDAO<Person> {
}

private class PersonDAOExtension extends PersonDAO {
}

PersonDAOExtension.class実行時に、特定のクラス ( ) について、メソッドに対してどのタイプが返されるかを調べたいgetById(..)(予想: Person.class)。

Typeリフレクションを使用すると、このメソッドから返されるジェネリックを見つけることができます。この場合、それは aですTypeVariable(ただしClass、階層内のいずれかのクラスが共変の戻り値の型を指定する場合は、 a である可能性もあります):

Method method = PersonDAOExtension.class.getMethod("getById", Integer.class);
Type genericReturnType = method.getGenericReturnType();
if (genericReturnType instanceof TypeVariable<?>) {
    TypeVariable<?> typeVariable = (TypeVariable<?>) genericReturnType;
    typeVariable.getName(); //results in "T"
}

実際の型を解決することは、スーパークラスとインターフェイスに再帰し、必要な型名が見つかるまで、パラメーター化された型のraw ( parameterizedType.getRawType()) およびactual ( ) 型引数を変換することを意味すると思います。parameterizedType.getActualTypeArguments()

誰かがこれを以前に行ったことがありますか?おそらく、これを達成するのに役立ついくつかのコードスニペットが用意されていますか? よろしくお願いします:)


ヒント:リフレクションを使用して実行時に次の情報を抽出できたので、生の型情報と実際の型情報が保持されます。

private abstract interface DAO<I, E>
private abstract class AbstractDAO<T> extends Object implements DAO<Integer, T> [raw type:DAO<I, E>]
private class PersonDAO extends AbstractDAO<Person> [raw type:AbstractDAO<T>]
private class PersonDAOExtension extends PersonDAO
4

3 に答える 3

11

最終的に解決策を見つけることができました。スーパークラスとインターフェイスに再帰し、型変数を渡された型引数に置き換えて、目的の基本クラスに到達します。

 /**
 * Resolves the actual generic type arguments for a base class, as viewed from a subclass or implementation.
 * 
 * @param <T> base type
 * @param offspring class or interface subclassing or extending the base type
 * @param base base class
 * @param actualArgs the actual type arguments passed to the offspring class
 * @return actual generic type arguments, must match the type parameters of the offspring class. If omitted, the
 * type parameters will be used instead.
 */
public static <T> Type[] resolveActualTypeArgs (Class<? extends T> offspring, Class<T> base, Type... actualArgs) {

    assert offspring != null;
    assert base != null;
    assert actualArgs.length == 0 || actualArgs.length == offspring.getTypeParameters().length;

    //  If actual types are omitted, the type parameters will be used instead.
    if (actualArgs.length == 0) {
        actualArgs = offspring.getTypeParameters();
    }
    // map type parameters into the actual types
    Map<String, Type> typeVariables = new HashMap<String, Type>();
    for (int i = 0; i < actualArgs.length; i++) {
        TypeVariable<?> typeVariable = (TypeVariable<?>) offspring.getTypeParameters()[i];
        typeVariables.put(typeVariable.getName(), actualArgs[i]);
    }

    // Find direct ancestors (superclass, interfaces)
    List<Type> ancestors = new LinkedList<Type>();
    if (offspring.getGenericSuperclass() != null) {
        ancestors.add(offspring.getGenericSuperclass());
    }
    for (Type t : offspring.getGenericInterfaces()) {
        ancestors.add(t);
    }

    // Recurse into ancestors (superclass, interfaces)
    for (Type type : ancestors) {
        if (type instanceof Class<?>) {
            // ancestor is non-parameterized. Recurse only if it matches the base class.
            Class<?> ancestorClass = (Class<?>) type;
            if (base.isAssignableFrom(ancestorClass)) {
                Type[] result = resolveActualTypeArgs((Class<? extends T>) ancestorClass, base);
                if (result != null) {
                    return result;
                }
            }
        }
        if (type instanceof ParameterizedType) {
            // ancestor is parameterized. Recurse only if the raw type matches the base class.
            ParameterizedType parameterizedType = (ParameterizedType) type;
            Type rawType = parameterizedType.getRawType();
            if (rawType instanceof Class<?>) {
                Class<?> rawTypeClass = (Class<?>) rawType;
                if (base.isAssignableFrom(rawTypeClass)) {

                    // loop through all type arguments and replace type variables with the actually known types
                    List<Type> resolvedTypes = new LinkedList<Type>();
                    for (Type t : parameterizedType.getActualTypeArguments()) {
                        if (t instanceof TypeVariable<?>) {
                            Type resolvedType = typeVariables.get(((TypeVariable<?>) t).getName());
                            resolvedTypes.add(resolvedType != null ? resolvedType : t);
                        } else {
                            resolvedTypes.add(t);
                        }
                    }

                    Type[] result = resolveActualTypeArgs((Class<? extends T>) rawTypeClass, base, resolvedTypes.toArray(new Type[] {}));
                    if (result != null) {
                        return result;
                    }
                }
            }
        }
    }

    // we have a result if we reached the base class.
    return offspring.equals(base) ? actualArgs : null;
}

魅力のように機能します:

resolveActualTypeArgs(PersonDAOExtension.class, DAO.class)

結果はIntegerPerson

resolveActualTypeArgs(AbstractDAO.class, DAO.class)

結果はIntegerT

resolveActualTypeArgs(LinkedList.class, Iterable.class, String.class)

結果はString

これを使用して、与えられた一連の DAO 実装のどれが Person を読み取ることができるかを調べることができます。

List<DAO<?, ?>> knownDAOs = ...

for (DAO<?, ?> daoImpl : knownDAOs) {
    Type[] types = resolveActualTypeArgs(daoImpl.getClass(), DAO.class);
    boolean canReadPerson = types[1] instanceof Class<?> && Person.class.isAssignableFrom((Class<?>) types[1]);
}

そして、これは、 a new PersonDAOExtension()、 a 、new PersonDAO()または aのいずれを渡すかに関係なく機能しnew AbstractDAO<Person>{}ます。

于 2013-06-25T15:51:05.373 に答える
11

Google Guava のTypeTokenクラスを使用して、メソッドの一般的な戻り値の型を 1 行で判別できました。

TypeToken.of(PersonDAOExtension.class)
        .resolveType(PersonDAOExtension.class.getMethod("getById", Integer.class).getGenericReturnType())
        .getRawType()

または、メソッドの戻り値の型ではなく、(受け入れられた回答で行ったように) クラスのジェネリック型を取得する場合は、次のようにします。

TypeToken.of(PersonDAOExtension.class)
        .resolveType(AbstractDAO.class.getTypeParameters()[0])
        .getRawType()

これらのソリューションは両方ともPerson.class期待どおりに戻ります。

Person受け入れられた回答に対するコメントから、指定された DAO がオブジェクトを受け入れることができるかどうかを知りたいだけのようです。これも API で実行できます。

(new TypeToken<DAO<?, Person>>() {})
        .isSupertypeOf(TypeToken.of(PersonDAOExtension.class))

Guava GitHub wiki には、これおよび他の Guava リフレクション ユーティリティの機能に関する適切な説明があります。

于 2014-06-29T08:25:42.090 に答える
0

以前も同様の問題がありました。私のソリューションでは、抽象クラスには次の名前のメソッドがありました

static void register(Class<? extends DAO> clazz, ? extends DAO daoInstance).

そして、抽象クラスにはMap、インスタンスへの参照を格納する がありました。シングルトンを使用しましたが、複数のインスタンスがある場合はマルチマップを使用できます。この手法を使用すると、リフレクションを取り除くことができ、Set登録したすべての実装とそのクラスも取得できます。

さらに情報が必要な場合は、いくつかの pojo クラスも登録できます。

public class DaoData{

    private Class<? extends DAO> daoClass;
    private Class<?> someArbitraryTypeClass;
    // ...
}

static void register(DaoData daoData, ? extends DAO daoInstance)

私はそれが最善の解決策ではないことを知っていますが、それは簡単で、仕事を成し遂げました.

于 2013-06-25T12:43:34.250 に答える