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


143

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

例:

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

回答:


156

1つの構造、私はかつてのように偶然出会った

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

だから、残念ながら私は完全には理解できないいくつかの反射魔法があるようです...すみません。


2
ただし、その場合は抽象として宣言してください。使用したい場合は、インラインでサブクラス化します。
スニコラス

42
これは非常に古く何らかの理由で受け入れられていますが、質問に答えないだけなので、私は反対票を投じました。それは考えていない問題で与えられた例では「スパイダーマン」を与えます。状況によっては間違いなく役立ちますが、尋ねられた質問に対しては機能しません。
Jon Skeet、2014年

15
ここまで来た人は誰も「それはできない」という答えを受け入れようとはしていません。私たちは絶望的だからここにいます。
Addison

14
私はこの例外を受け取ります:Exception in thread "main" java.lang.ClassCastException: java.lang.Class cannot be cast to java.lang.reflect.ParameterizedType 制約が何であるかわかりません。
2016

4
@Dishのように、私はちょうど得ますjava.lang.ClassCastException: java.lang.Class cannot be cast to java.lang.reflect.ParameterizedType
ケニーワイランド

133

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

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

次に、これは、チェックされている具象型から階層の1レベル上のジェネリッククラスにジェネリック型情報を提供します。つまり、ジェネリック型パラメーターを持つ抽象親クラスは、自身の具象実装の型パラメーターに対応する具象型を見つけることができます。それが直接それを継承しています。このクラスが非抽象的でインスタンス化されている場合、または具体的な実装が2レベル下である場合、これは機能しません(少しジミーを行うと、1を超える、または最低クラスまでの任意の所定のレベルに適用できます) Xジェネリック型パラメーターなど)。

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

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

'us'を、このコードを含むジェネリック型を持つ抽象クラスとします。これを大まかに裏返しに読んでください:

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

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

具体的な子供が任意のホップ数離れている場合、または具体的で最終的でない場合はトリッキーになり、特に(さまざまな深さの)子供が独自のジェネリックを持つことが予想される場合はトリッキーになります。しかし、通常はこれらの考慮事項に基づいて設計できるため、これでほとんどの方法が得られます。

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


3
「JavaでClassインスタンスが関連付けられていないジェネリックパラメーターを取得する方法がわからない」と答えるだけです。これは、ジェネリッククラスのパラメーターが「 「ワイルドカード式」(たとえば、?はSomeClassを拡張します)または「タイプ変数」(たとえば:<T>で、Tは一般的なサブクラスに由来します)。どちらの場合も、返された「Type」インスタンスを「Class」にキャストすることはできません。各VMに、Typeインターフェースを実装するがjava.lang.Classから直接派生しない異なる実装がある可能性があるためです。
Roberto Andrade 2013

19

実際、私はこれを機能させました。次のスニペットを考えてみましょう:

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を使用しています


12
-1:これは問題の問題を解決しません。実際の実行時の型ではなく、メソッドシグネチャで宣言された型のみを提供します。
Michael Borgwardt、2011

5
OPの質問には答えないかもしれませんが、それは便利なテクニックです。
dnault 2013

3
これはまったく別の質問に対する答えです。
Dawood ibnカリーム2014

m.getParameterTypes()を呼び出して、すべての実際のタイプを取得できます。この例では「リスト」を返します。
Lee Meador 2016年

genericParameterTypes [i]がParameterizedTypeのインスタンスではない場合、それはクラスである可能性があります。つまり、メソッドへの特定の引数はパラメーター化されていません。
Lee Meador 2016年

18

「匿名クラス」のトリックスーパータイプトークンのアイデアを適用することで、実際に解決策があります。

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>() {}元の(簡単な)の代わりに、new ArrayList<SpiderMan>()。匿名クラス(可能な場合)を使用すると、コンパイラーがSpiderMantypeパラメーターに指定された型引数に関する情報を確実に保持しますList<?>。ほら!


これは間接的に質問に答えます。元の問題には解決策がありません。型がそのジェネリックパラメーターを保持するには、サブクラスなどの別の型に埋め込む必要があります。この例は、呼び出しコードを変更して、chill()のジェネリック型パラメーターを回復できるようにする方法を示しています。
Florian F

参考までに、最初のリンクは無効です
Ashvin Sharma

@AshvinSharma同じ資料がここにあると思い
GaëlGuéhéneuc

7

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

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

パラメーター化されたインターフェイスのジェネリックパラメーターを取得するための@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
}

これは、元の投稿が求めていたものではありません。ここでは、のサブクラスを作成していますAwesomeable<Beer>。その場合、タイプ情報は保持されます。new Awesomable<Beer> ()メソッドに渡すとwtは機能しません。
フロリアンF

@FlorianF この場合のAwesomable<Beer>ようEstrellaGaliciaに具象サブクラスの明示的な定義なしでオンザフライで渡すと、それでもパラメーター化されたタイプが取得されます。今すぐ実行しました:System.out.println("Type is: " + new Awesomable<Beer>() {}.parameterizedType());---> タイプは次のとおりです:ParameterizedStuff $ Beer
Campa

6

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


Javaは実行時にこれをメタデータとして保存できますが、そうではありませんか 私はそれが望んでいた。不運。
シムニン2009

1
@ user99475:いいえ。私は正しい、アンドレイは間違っている。彼は私の回答の後半で言及しているタイプ情報を参照していますが、それは質問が求めているものではありません。
Michael Borgwardt、2011

5

@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が行うことです。



1

あなたは私がここで見つけたこの例のようなリフレクションでジェネリックパラメーターのタイプを取得することができます

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

V of Home <K、V>を取得するには、同じことをどのように行いますか?
Rafael Sanches 2016年

1

Javaのジェネリック型の消去のため、質問にすばやく答えることはできません。

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

new ArrayList<SpideMan>(){}

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

リストでこれを行うことをお勧めしますが、これはリスナーの実装です。

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

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

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


0

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


ええ、それが今私がやっていることです。しかし、今はリストが空でもタイプを知りたいです。しかし、4人の男が間違っていることはありません。皆さん、ありがとうございました)!
シムニン2009

19
本当じゃない。トリッキーですが、次の投稿でParameterizedTypeがそれを可能にすることがわかります。
Edmondo1984

1
同意できます。DerMikeの例がそれを概説しています(私のために働いた)。
Mac

「4人の男は間違いない」での爆笑。この答えは正しいです。
Dawood ibnカリーム2014

0

これを受け入れるか返すことを期待するメソッドのためにこれをコーディングしました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

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

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

sun.reflect.generics.reflectiveObjects.TypeVariableImpl:これは、スレッドで私に「メイン」とjava.lang.ClassCastException例外を与える
リュイス・マルティネス

「params」はいくつかのサブインターフェースを持つ「タイプ」です。TypeVariableImplはメソッドの引数に使用されるものであり、それが表すクラスではなく、<>内のジェネリックの名前を通知します。
Lee Meador 2016年

0

私がこのコードスニペットを読むのは大変だったので、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

ここに別のトリックがあります。汎用可変引数配列を使用する

import java.util.ArrayList;

class TypedArrayList<E> extends ArrayList<E>
{
    @SafeVarargs
    public TypedArrayList (E... typeInfo)
    {
        // Get generic type at runtime ...
        System.out.println (typeInfo.getClass().getComponentType().getTypeName());
    }
}

public class GenericTest
{
    public static void main (String[] args)
    {
        // No need to supply the dummy argument
        ArrayList<Integer> ar1 = new TypedArrayList<> ();
        ArrayList<String> ar2 = new TypedArrayList<> ();
        ArrayList<?> ar3 = new TypedArrayList<> ();
    }
}

0

多くの人がgetGenericSuperclass()解決策に傾いていることに気づきました:

class RootGeneric<T> {
  public Class<T> persistentClass = (Class<T>)
    ((ParameterizedType)getClass().getGenericSuperclass())
      .getActualTypeArguments()[0];
}

ただし、このソリューションはエラーが発生しやすくなります。それはなりません子孫でジェネリック医薬品がある場合は正常に動作します。このことを考慮:

class Foo<S> extends RootGeneric<Integer> {}

class Bar extends Foo<Double> {}

どのタイプになりBar.persistentClassますか?Class<Integer>?いいえ、そうなりますClass<Double>。これは、getClass()常に最上位のクラス(Barこの場合)を返すために発生し、その総称スーパークラスはFoo<Double>です。したがって、引数の型はになりますDouble

失敗しない信頼できるソリューションが必要な場合は、2つお勧めします。

  1. を使用しGuavaます。この目的のために作成されたクラスがありますcom.google.common.reflect.TypeToken。それはすべてのコーナーケースをうまく処理し、いくつかのより良い機能を提供します。欠点は、追加の依存関係です。このクラスを使用した場合、コードは次のようにシンプルで明確になります。
class RootGeneric<T> {
  @SuppressWarnings("unchecked")
  public final Class<T> persistentClass = (Class<T>) (new TypeToken<T>(getClass()) {}.getType());
}
  1. 以下のカスタムメソッドを使用します。上記のGuavaクラスと同様に、大幅に簡略化されたロジックを実装します。ただし、エラーが発生しやすいとは限りません。ただし、一般的な子孫の問題は解決します。
abstract class RootGeneric<T> {
  @SuppressWarnings("unchecked")
  private Class<T> getTypeOfT() {
    Class<T> type = null;
    Class<?> iter = getClass();
    while (iter.getSuperclass() != null) {
      Class<?> next = iter.getSuperclass();
      if (next != null && next.isAssignableFrom(RootGeneric.class)) {
        type =
            (Class<T>)
                ((ParameterizedType) iter.getGenericSuperclass()).getActualTypeArguments()[0];
        break;
      }
      iter = next;
    }
    if (type == null) {
      throw new ClassCastException("Cannot determine type of T");
    }
    return type;
  }
}

-1

使用する:

Class<?> typeOfTheList = aListWithTypeSpiderMan.toArray().getClass().getComponentType();

これは間違っています。toArray()を返しますObject[]。特定のサブタイプであることに依存することはできません。
Radiodef
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.