web-dev-qa-db-ja.com

リフレクションを使用してJavaのジェネリックパラメーターの型を取得します

ジェネリックパラメーターの型を取得することは可能ですか?

例:

public final class Voodoo {
    public static void chill(List<?> aListWithTypeSpiderMan) {
        // Here I'd like to get the Class-Object 'SpiderMan'
        Class typeOfTheList = ???;
    }

    public static void main(String... args) {
        chill(new ArrayList<SpiderMan>());
    }
}
125
cimnine

一つの構造、私はかつてのようにつまずいた

Class<T> persistentClass = (Class<T>)
   ((ParameterizedType)getClass().getGenericSuperclass())
      .getActualTypeArguments()[0];

そのため、私は不幸にも完全には理解していないいくつかのリフレクションマジックがあるようです...ごめんなさい。

145
DerMike

私は@DerMikeからの答えを分解して説明したい:

まず、型消去は、実行時にJDK eliminates型情報を意味するものではありません。これは、コンパイル時の型チェックとランタイム型の互換性を同じ言語で共存させるための方法です。このコードブロックが示すように、JDKは消去された型情報を保持します。これは、チェックされたキャストなどに関連付けられていないだけです。

第二に、これは、ジェネリック型情報をジェネリッククラスに、チェックされている具象型からちょうど1レベル上の階層に提供します。ジェネリック型パラメーターを持つ抽象親クラスは、直接が継承するそれ自体の具象実装の型パラメーターに対応する具象型を見つけることができます。このクラスが抽象的でなく、インスタンス化されている場合、または具体的な実装が2レベル下の場合、これは機能しません(わずかな混乱が1つを超える、または最下位のクラスまでの事前に決められた数のレベルに適用される可能性がありますが) Xジェネリック型パラメーターなどを使用)。

とにかく、説明に。参照しやすいように、次のコードを再び行に分けて示します。

 1#クラスgenericParameter0OfThisClass = 
 2#(クラス)
 3#((ParameterizedType)
 4#getClass()
 5#.getGenericSuperclass ())
 6#.getActualTypeArguments()[0]; 

このコードを含むジェネリック型を持つ抽象クラスを「us」とします。これを大まかに裏返す:

  • 行4は、現在の具象クラスのクラスインスタンスを取得します。これは、直接の子孫の具象型を識別します。
  • 行5は、そのクラスのスーパータイプをタイプとして取得します。これが私たちです。パラメータ型であるため、ParameterizedTypeに安全にキャストできます(3行目)。重要な点は、JavaがこのTypeオブジェクトを決定するときに、子に存在する型情報を使用して、型情報を新しいParameterizedTypeインスタンスの型パラメーターに関連付けることです。これで、ジェネリックの具象型にアクセスできます。
  • 6行目は、クラスコードで宣言されている順序でジェネリックにマップされた型の配列を取得します。この例では、最初のパラメーターを引き出します。これはタイプとして返​​されます。
  • 行2は、クラスに返された最終的なタイプをキャストします。これは、ジェネリック型パラメーターがどのタイプを取ることができるかを知っており、それらがすべてクラスであることを確認できるためです(Javaではどのようにジェネリックパラメーターを取得するのかわかりません)実際には、それに関連付けられたClassインスタンスはありません)。

...それでおしまいです。したがって、型情報を独自の具体的な実装から自分自身にプッシュし、それを使用してクラスハンドルにアクセスします。 getGenericSuperclass()を2倍にして2つのレベルに移動するか、getGenericSuperclass()を削除して具体的な型として自分自身の値を取得することができます(注意:これらのシナリオはまだテストしていません。

具体的な子が任意のホップ数離れている場合、または具体的で最終的なものではない場合、特に(さまざまな深さの)子に独自のジェネリックがあると予想される場合は特に注意が必要です。ただし、通常はこれらの考慮事項に基づいて設計することができるため、ほとんどの方法でこれが可能になります。

これが誰かを助けたことを願っています!この投稿は古代のものだと思います。私はおそらくこの説明を切り抜いて、他の質問のために保管します。

125
Mark McKenna

実際、私はこれを機能させました。次のスニペットを検討してください。

Method m;
Type[] genericParameterTypes = m.getGenericParameterTypes();
for (int i = 0; i < genericParameterTypes.length; i++) {
     if( genericParameterTypes[i] instanceof ParameterizedType ) {
                Type[] parameters = ((ParameterizedType)genericParameterTypes[i]).getActualTypeArguments();
//parameters[0] contains Java.lang.String for method like "method(List<String> value)"

     }
 }

私はjdk 1.6を使用しています

20
Andrey Rodionov

"匿名クラス"トリックSuper Type Tokens からのアイデアを適用することにより、実際に解決策があります。

public final class Voodoo {
    public static void chill(final List<?> aListWithSomeType) {
        // Here I'd like to get the Class-Object 'SpiderMan'
        System.out.println(aListWithSomeType.getClass().getGenericSuperclass());
        System.out.println(((ParameterizedType) aListWithSomeType
            .getClass()
            .getGenericSuperclass()).getActualTypeArguments()[0]);
    }
    public static void main(String... args) {
        chill(new ArrayList<SpiderMan>() {});
    }
}
class SpiderMan {
}

トリックは、元の(単純な)new ArrayList<SpiderMan>() {}の代わりに、anonymousクラスnew ArrayList<SpiderMan>()を作成することにあります。 (可能な場合)匿名クラスを使用すると、型パラメーターList<?>に指定された型引数SpiderManに関する情報がコンパイラーによって保持されます。ほら!

タイプの消去のため、リストのタイプを知る唯一の方法は、メソッドにパラメーターとしてタイプを渡すことです。

public class Main {

    public static void main(String[] args) {
        doStuff(new LinkedList<String>(), String.class);

    }

    public static <E> void doStuff(List<E> list, Class<E> clazz) {

    }

}
7
Jared Russell

パラメータ化されたインターフェイスのジェネリックパラメータを取得するための@DerMikeの回答の付録(重複を避けるためにJava-8のデフォルトメソッド内で#getGenericInterfaces()メソッドを使用):

import Java.lang.reflect.ParameterizedType; 

public class ParametrizedStuff {

@SuppressWarnings("unchecked")
interface Awesomable<T> {
    default Class<T> parameterizedType() {
        return (Class<T>) ((ParameterizedType)
        this.getClass().getGenericInterfaces()[0])
            .getActualTypeArguments()[0];
    }
}

static class Beer {};
static class EstrellaGalicia implements Awesomable<Beer> {};

public static void main(String[] args) {
    System.out.println("Type is: " + new EstrellaGalicia().parameterizedType());
    // --> Type is: ParameterizedStuff$Beer
}
6
Campa

いいえ、それは不可能です。下位互換性の問題のため、Javaのジェネリックは type erasure に基づいています。実行時には、非ジェネリックListオブジェクトのみがあります。実行時の型パラメーターに関する情報はいくつかありますが、オブジェクトインスタンスではなく、クラス定義に存在します(つまり、「 このフィールドの定義はどの汎用タイプを使用しますか? 」と尋ねることができます)。

6

@bertolamiが指摘したように、変数型を使用して将来の値(typeOfList変数の内容)を取得することはできません。

それでも、次のようにクラスをパラメーターとして渡すことができます。

public final class voodoo {
    public static void chill(List<T> aListWithTypeSpiderMan, Class<T> clazz) {
        // Here I'd like to get the Class-Object 'SpiderMan'
        Class typeOfTheList = clazz;
    }

    public static void main(String... args) {
        chill(new List<SpiderMan>(), Spiderman.class );
    }
}

これは、クラス変数を ActivityInstrumentationTestCase2 のコンストラクターに渡す必要がある場合にGoogleが行うこととほぼ同じです。

5
Snicolas

いいえ、不可能です。

クラスがそのルールの唯一の例外であり、それがちょっとしたハックであっても、フィールドのジェネリック型を取得できます。

Javaのジェネリックの種類を知る を参照してください。

3
cletus

Javaジェネリック型消去のため、Questionという答えは簡単ではありません。

より長い答えは、次のようなリストを作成した場合です。

new ArrayList<SpideMan>(){}

この場合、ジェネリック型は上記の新しい匿名クラスのジェネリックスーパークラスに保持されます。

リストでこれを行うことをお勧めするわけではありませんが、リスナーの実装です:

new Listener<Type>() { public void doSomething(Type t){...}}

また、スーパークラスとスーパーインターフェースのジェネリックタイプを外挿することはJVM間で変化するため、ジェネリックソリューションはいくつかの答えが示唆するほど簡単ではありません。

ここで私はそれをやった。

0
TacB0sS

Iterable<?...>を受け入れるか返すことを期待するメソッド用にこれをコーディングしました。コードは次のとおりです。

/**
 * Assuming the given method returns or takes an Iterable<T>, this determines the type T.
 * T may or may not extend WindupVertexFrame.
 */
private static Class typeOfIterable(Method method, boolean setter)
{
    Type type;
    if (setter) {
        Type[] types = method.getGenericParameterTypes();
        // The first parameter to the method expected to be Iterable<...> .
        if (types.length == 0)
            throw new IllegalArgumentException("Given method has 0 params: " + method);
        type = types[0];
    }
    else {
        type = method.getGenericReturnType();
    }

    // Now get the parametrized type of the generic.
    if (!(type instanceof ParameterizedType))
        throw new IllegalArgumentException("Given method's 1st param type is not parametrized generic: " + method);
    ParameterizedType pType = (ParameterizedType) type;
    final Type[] actualArgs = pType.getActualTypeArguments();
    if (actualArgs.length == 0)
        throw new IllegalArgumentException("Given method's 1st param type is not parametrized generic: " + method);

    Type t = actualArgs[0];
    if (t instanceof Class)
        return (Class<?>) t;

    if (t instanceof TypeVariable){
        TypeVariable tv =  (TypeVariable) actualArgs[0];
        AnnotatedType[] annotatedBounds = tv.getAnnotatedBounds();///
        GenericDeclaration genericDeclaration = tv.getGenericDeclaration();///
        return (Class) tv.getAnnotatedBounds()[0].getType();
    }

    throw new IllegalArgumentException("Unknown kind of type: " + t.getTypeName());
}
0
Ondra Žižka

Javaのジェネリックはコンパイル時にのみ考慮されるため、これは不可能です。したがって、Javaジェネリックはある種のプリプロセッサです。ただし、リストのメンバーの実際のクラスを取得できます。

0
bertolami

変数からジェネリックパラメーターを取得することはできません。ただし、メソッドまたはフィールドの宣言からはできます。

Method method = getClass().getDeclaredMethod("chill", List.class);
Type[] params = method.getGenericParameterTypes();
ParameterizedType firstParam = (ParameterizedType) params[0];
Type[] paramsOfFirstGeneric = firstParam.getActualTypeArguments();
0
Anton Pogonets

私が見つけたこの例のように、リフレクションを使用して汎用パラメーターのタイプを取得できます here

import Java.lang.reflect.ParameterizedType;
import Java.lang.reflect.Type;

public class Home<E> {
    @SuppressWarnings ("unchecked")
    public Class<E> getTypeParameterClass(){
        Type type = getClass().getGenericSuperclass();
        ParameterizedType paramType = (ParameterizedType) type;
        return (Class<E>) paramType.getActualTypeArguments()[0];
    }

    private static class StringHome extends Home<String>{}
    private static class StringBuilderHome extends Home<StringBuilder>{}
    private static class StringBufferHome extends Home<StringBuffer>{}   

    /**
     * This prints "String", "StringBuilder" and "StringBuffer"
     */
    public static void main(String[] args) throws InstantiationException, IllegalAccessException {
        Object object0 = new StringHome().getTypeParameterClass().newInstance();
        Object object1 = new StringBuilderHome().getTypeParameterClass().newInstance();
        Object object2 = new StringBufferHome().getTypeParameterClass().newInstance();
        System.out.println(object0.getClass().getSimpleName());
        System.out.println(object1.getClass().getSimpleName());
        System.out.println(object2.getClass().getSimpleName());
    }
}
0
madx

このコードスニペットを読むのは大変でしたが、読みやすい2行に分割しました。

// assuming that the Generic Type parameter is of type "T"
ParameterizedType p = (ParameterizedType) getClass().getGenericSuperclass();
Class<T> c =(Class<T>)p.getActualTypeArguments()[0];

メソッドにパラメーターを持たずに、Typeパラメーターのインスタンスを作成したかったのです。

publc T getNewTypeInstance(){
    ParameterizedType p = (ParameterizedType) getClass().getGenericSuperclass();
    Class<T> c =(Class<T>)p.getActualTypeArguments()[0];

    // for me i wanted to get the type to create an instance
    // from the no-args default constructor
    T t = null;
    try{
        t = c.newInstance();
    }catch(Exception e){
        // no default constructor available
    }
    return t;
}
0