ジェネリック型Tのクラスインスタンスを取得するにはどうすればよいですか?


700

ジェネリッククラスがありFoo<T>ます。のメソッドでFoo、型のクラスインスタンスを取得したいのですが、T呼び出すことができませんT.class

それを使用して回避するための好ましい方法は何T.classですか?


2
この質問に答えてみてください。stackoverflow.com/questions/1942644/...
エミール



1
import com.fasterxml.jackson.core.type.TypeReference; new TypeReference<T>(){}
Bogdan Shulga

回答:


567

短い答えは、Javaのジェネリック型パラメーターのランタイム型を見つける方法はないということです。詳細については、Javaチュートリアルの型消去に関する章を読むことをお勧めします。

これに対する一般的な解決策Classは、型パラメーターのをジェネリック型のコンストラクターに渡すことです。たとえば、

class Foo<T> {
    final Class<T> typeParameterClass;

    public Foo(Class<T> typeParameterClass) {
        this.typeParameterClass = typeParameterClass;
    }

    public void bar() {
        // you can access the typeParameterClass here and do whatever you like
    }
}

73
この答えは有効な解決策を提供しますが、実行時にジェネリック型を見つける方法がないと言うのは不正確です。タイプの消去は、全面的な消去よりもはるかに複雑であることがわかります。私の答えは、クラスのジェネリック型を取得する方法を示しています。
Ben Thurley、

3
@BenThurley Neatトリックですが、私が見る限り、使用できる汎用のスーパータイプがある場合にのみ機能します。私の例では、Foo <T>でTの型を取得できません。
ZsoltTörök2013年

@webjockeyいいえ、すべきではありません。typeParameterClassコンストラクタでデフォルトの割り当てなしでを割り当てることは、まったく問題ありません。もう一度設定する必要はありません。
Adowrath 2017

これが最初に思い浮かぶソリューションですが、オブジェクトを作成/開始するのはあなたではない場合があります。そのため、コンストラクターを使用できなくなります。たとえば、データベースからJPAエンティティを取得しているとき。
Paramvir Singh Karwal

233

クラスパスに追加の依存関係を追加せずに自分でこれを行う方法を探していました。調査の結果、一般的なスーパータイプがあれば可能であることわかりました。汎用レイヤースーパータイプを使用してDAOレイヤーを操作していたので、これは私にとっては問題ありませんでした。これがあなたのシナリオに当てはまる場合、それは私見の最も近いアプローチです。

私が遭遇したほとんどのジェネリックのユースケースには、たとえばList<T>for ArrayList<T>GenericDAO<T>for DAO<T>などのある種のジェネリックスーパータイプがあります。

ピュアJavaソリューション

記事「Javaで実行時にジェネリック型にアクセスする」では、純粋なJavaを使用してそれを行う方法について説明しています。

@SuppressWarnings("unchecked")
public GenericJpaDao() {
  this.entityBeanType = ((Class) ((ParameterizedType) getClass()
      .getGenericSuperclass()).getActualTypeArguments()[0]);
}

春のソリューション

私のプロジェクトではSpringを使用していましたが、Springにはタイプを見つけるための便利なユーティリティメソッドがあるため、さらに優れています。見た目が最も良く、これは私にとって最良のアプローチです。Springを使用していない場合は、独自のユーティリティメソッドを作成できます。

import org.springframework.core.GenericTypeResolver;

public abstract class AbstractHibernateDao<T extends DomainObject> implements DataAccessObject<T>
{

    @Autowired
    private SessionFactory sessionFactory;

    private final Class<T> genericType;

    private final String RECORD_COUNT_HQL;
    private final String FIND_ALL_HQL;

    @SuppressWarnings("unchecked")
    public AbstractHibernateDao()
    {
        this.genericType = (Class<T>) GenericTypeResolver.resolveTypeArgument(getClass(), AbstractHibernateDao.class);
        this.RECORD_COUNT_HQL = "select count(*) from " + this.genericType.getName();
        this.FIND_ALL_HQL = "from " + this.genericType.getName() + " t ";
    }

1
resolveTypeArgument引数の意味を明確にしてください
gstackoverflow

getClass()は、実行時に特定のオブジェクトのクラスを返すjava.lang.Objectのメソッドです。これは、型を解決するオブジェクトです。AbstractHibernateDao.classは、ジェネリック型クラス階層の基本クラスまたはスーパークラスの名前です。インポート文が含まれているので、簡単にドキュメントを見つけて自分で確認できます。これは、docs.spring.io / spring / docs / current / javadoc
Ben Thurley

バージョン4.3.6以降のSpringソリューションとは何ですか。Spring 4.3.6では機能しません。
Erlan

1
「Pure Javaソリューション」のリンクが壊れています。現在はblog.xebia.com/acessing-generic-types-at-runtime-in-java
Nick Breen

1
@ AlikElzin-kilakaは、SpringクラスGenericTypeResolverを使用してコンストラクターで初期化されます。
Ben Thurley

103

ただし、小さな抜け穴があります。Fooクラスを抽象として定義した場合です。つまり、次のようにクラスをインスタンス化する必要があります。

Foo<MyType> myFoo = new Foo<MyType>(){};

(末尾の二重ブレースに注意してください。)

これでT、実行時にのタイプを取得できます。

Type mySuperclass = myFoo.getClass().getGenericSuperclass();
Type tType = ((ParameterizedType)mySuperclass).getActualTypeArguments()[0];

ただし、これmySuperclassはクラス定義のスーパークラスである必要があることに注意してください。T

それも非常にエレガントではありませんが、あなたが好むのnew Foo<MyType>(){}new Foo<MyType>(MyType.class);あなたのコードでするのかを決める必要があります。


例えば:

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

import java.util.ArrayDeque;
import java.util.Deque;
import java.util.NoSuchElementException;

/**
 * Captures and silently ignores stack exceptions upon popping.
 */
public abstract class SilentStack<E> extends ArrayDeque<E> {
  public E pop() {
    try {
      return super.pop();
    }
    catch( NoSuchElementException nsee ) {
      return create();
    }
  }

  public E create() {
    try {
      Type sooper = getClass().getGenericSuperclass();
      Type t = ((ParameterizedType)sooper).getActualTypeArguments()[ 0 ];

      return (E)(Class.forName( t.toString() ).newInstance());
    }
    catch( Exception e ) {
      return null;
    }
  }
}

次に:

public class Main {
    // Note the braces...
    private Deque<String> stack = new SilentStack<String>(){};

    public static void main( String args[] ) {
      // Returns a new instance of String.
      String s = stack.pop();
      System.out.printf( "s = '%s'\n", s );
    }
}

5
これはここでの最良の答えです。また、価値のあるものとして、これはGoogle Guiceがクラスをバインドするために使用する戦略ですTypeLiteral
ATG

14
このオブジェクト構築メソッドが使用されるたびに、新しい匿名クラスが作成されることに注意してください。つまり、この方法ab作成された2つのオブジェクトはどちらも同じクラスを拡張しますが、同一のインスタンスクラスはありません。 a.getClass() != b.getClass()
Martin Serrano、

3
これが機能しないシナリオが1つあります。FooがSerializableなどのインターフェースを実装する必要がある場合、クラスのインスタンス化が行われない限り、匿名クラスはSerializableにはなりません。Fooから派生した匿名クラスを作成するシリアル化可能なファクトリクラスを作成して回避策を試みましたが、何らかの理由で、getActualTypeArgumentsが実際のクラスではなくジェネリック型を返します。例:(new FooFactory <MyType>())。createFoo()
Lior Chaga

38

標準的なアプローチ/回避策/解決策は、次のようにclassオブジェクトをコンストラクタに追加することです。

 public class Foo<T> {

    private Class<T> type;
    public Foo(Class<T> type) {
      this.type = type;
    }

    public Class<T> getType() {
      return type;
    }

    public T newInstance() {
      return type.newInstance();
    }
 }

1
しかし、実際の使用では@autowiredできないように見えました。回避策はありますか?
Alfred Huang

@AlfredHuang回避策は、これを実行し、自動配線に依存しないクラスのBeanを作成することです。
Calebj

20

ジェネリックな抽象スーパークラスがあると想像してください:

public abstract class Foo<? extends T> {}

そして、Tを拡張するジェネリックBarでFooを拡張する2番目のクラスがあります。

public class Second extends Foo<Bar> {}

(bert bruynoogheの回答から)をBar.class選択し、インスタンスTypeを使用して推論することで、Fooクラスのクラスを取得できClassます。

Type mySuperclass = myFoo.getClass().getGenericSuperclass();
Type tType = ((ParameterizedType)mySuperclass).getActualTypeArguments()[0];
//Parse it as String
String className = tType.toString().split(" ")[1];
Class clazz = Class.forName(className);

この操作は理想的ではないことに注意する必要があります。そのため、これに対する複数の計算を回避するために計算値をキャッシュすることをお勧めします。一般的な用途の1つは、一般的なDAOの実装です。

最終的な実装:

public abstract class Foo<T> {

    private Class<T> inferedClass;

    public Class<T> getGenericClass(){
        if(inferedClass == null){
            Type mySuperclass = getClass().getGenericSuperclass();
            Type tType = ((ParameterizedType)mySuperclass).getActualTypeArguments()[0];
            String className = tType.toString().split(" ")[1];
            inferedClass = Class.forName(className);
        }
        return inferedClass;
    }
}

他の関数のFooクラスまたはBarクラスから呼び出された場合、返される値はBar.classです。


1
toString().split(" ")[1]それが問題でした、避けてください"class "
IgniteCoders '10年

16

これが実用的な解決策です:

@SuppressWarnings("unchecked")
private Class<T> getGenericTypeClass() {
    try {
        String className = ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0].getTypeName();
        Class<?> clazz = Class.forName(className);
        return (Class<T>) clazz;
    } catch (Exception e) {
        throw new IllegalStateException("Class is not parametrized with generic type!!! Please use extends <> ");
    }
} 

注: スーパークラスとしてのみ使用できます

  1. 型付きクラス(Child extends Generic<Integer>)で拡張する必要があります

または

  1. 匿名の実装として作成する必要があります(new Generic<Integer>() {};

3
getTypeNameはtoStringを呼び出すため、.getActualTypeArguments()[0] .toString();で置き換えることができます。
Yaroslav Kovbas


9

他の人が提案したクラスよりも良いルートは、クラスで実行したことを実行できるオブジェクトを渡すことです。たとえば、新しいインスタンスを作成します。

interface Factory<T> {
  T apply();
}

<T> void List<T> make10(Factory<T> factory) {
  List<T> result = new ArrayList<T>();
  for (int a = 0; a < 10; a++)
    result.add(factory.apply());
  return result;
}

class FooFactory<T> implements Factory<Foo<T>> {
  public Foo<T> apply() {
    return new Foo<T>();
  }
}

List<Foo<Integer>> foos = make10(new FooFactory<Integer>());

@リッキー・クラークソン:このファクトリーがパラメーター化されたfooを返す方法がわかりません。これからFoo <T>を取得する方法を説明していただけますか?これは、パラメータ化されていないFooのみを与えるようです。make10のTは単にFooではありませんか?
ib84

@ ib84コードを修正しました。私が最初に答えを書いたとき、Fooがパラメーター化されているのを見逃していたようです。
リッキークラークソン

9

抽象ジェネリッククラスでこの問題が発生しました。この特定のケースでは、解決策はより簡単です。

abstract class Foo<T> {
    abstract Class<T> getTClass();
    //...
}

以降の派生クラス:

class Bar extends Foo<Whatever> {
    @Override
    Class<T> getTClass() {
        return Whatever.class;
    }
}

はい、そうですが、このクラスを拡張するときに実行する必要があることを最小限にしたいと思います。droidplの回答をチェック
Paramvir Singh Karwal

5

私はこの問題に対して(醜いが効果的な)解決策を持っています、私は最近使用しました:

import java.lang.reflect.TypeVariable;


public static <T> Class<T> getGenericClass()
{
    __<T> ins = new __<T>();
    TypeVariable<?>[] cls = ins.getClass().getTypeParameters(); 

    return (Class<T>)cls[0].getClass();
}

private final class __<T> // generic helper class which does only provide type information
{
    private __()
    {
    }
}


3

私はそれを行うための一般的で簡単な方法を見つけました。私のクラスでは、クラス定義での位置に従ってジェネリック型を返すメソッドを作成しました。次のようなクラス定義があるとします。

public class MyClass<A, B, C> {

}

次に、タイプを永続化するいくつかの属性を作成します。

public class MyClass<A, B, C> {

    private Class<A> aType;

    private Class<B> bType;

    private Class<C> cType;

// Getters and setters (not necessary if you are going to use them internally)

    } 

次に、ジェネリック定義のインデックスに基づいて型を返すジェネリックメソッドを作成できます。

   /**
     * Returns a {@link Type} object to identify generic types
     * @return type
     */
    private Type getGenericClassType(int index) {
        // To make it use generics without supplying the class type
        Type type = getClass().getGenericSuperclass();

        while (!(type instanceof ParameterizedType)) {
            if (type instanceof ParameterizedType) {
                type = ((Class<?>) ((ParameterizedType) type).getRawType()).getGenericSuperclass();
            } else {
                type = ((Class<?>) type).getGenericSuperclass();
            }
        }

        return ((ParameterizedType) type).getActualTypeArguments()[index];
    }

最後に、コンストラクタでメソッドを呼び出し、各タイプのインデックスを送信します。完全なコードは次のようになります。

public class MyClass<A, B, C> {

    private Class<A> aType;

    private Class<B> bType;

    private Class<C> cType;


    public MyClass() {
      this.aType = (Class<A>) getGenericClassType(0);
      this.bType = (Class<B>) getGenericClassType(1);
      this.cType = (Class<C>) getGenericClassType(2);
    }

   /**
     * Returns a {@link Type} object to identify generic types
     * @return type
     */
    private Type getGenericClassType(int index) {

        Type type = getClass().getGenericSuperclass();

        while (!(type instanceof ParameterizedType)) {
            if (type instanceof ParameterizedType) {
                type = ((Class<?>) ((ParameterizedType) type).getRawType()).getGenericSuperclass();
            } else {
                type = ((Class<?>) type).getGenericSuperclass();
            }
        }

        return ((ParameterizedType) type).getActualTypeArguments()[index];
    }
}

2

他の回答で説明されているように、これを使用するには ParameterizedTypeアプローチするには、クラスを拡張する必要がありますが、それは、それを拡張するまったく新しいクラスを作成するための余分な作業のようです...

したがって、クラスを抽象化すると、クラスを拡張しなければならず、サブクラス化の要件が満たされます。(ロンボクの@Getterを使用)。

@Getter
public abstract class ConfigurationDefinition<T> {

    private Class<T> type;
    ...

    public ConfigurationDefinition(...) {
        this.type = (Class<T>) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
        ...
    }
}

次に、新しいクラスを定義せずに拡張します。(最後の{}に注意してください...拡張されていますが、何も上書きしないでください-必要な場合を除きます)。

private ConfigurationDefinition<String> myConfigA = new ConfigurationDefinition<String>(...){};
private ConfigurationDefinition<File> myConfigB = new ConfigurationDefinition<File>(...){};
...
Class stringType = myConfigA.getType();
Class fileType = myConfigB.getType();

2

ジェネリッククラスがあるので、次のような変数があると思います。

private T t;

(この変数はコンストラクタで値を取得する必要があります)

その場合は、次のメソッドを作成するだけです。

Class<T> getClassOfInstance()
{
    return (Class<T>) t.getClass();
}

それが役に立てば幸い!


1
   public <T> T yourMethodSignature(Class<T> type) {

        // get some object and check the type match the given type
        Object result = ...            

        if (type.isAssignableFrom(result.getClass())) {
            return (T)result;
        } else {
            // handle the error
        }
   }

1

ジェネリックスを使用しているクラス/インターフェースを拡張または実装する場合、既存のクラス/インターフェースをまったく変更せずに、親クラス/インターフェースのジェネリック型を取得できます。

3つの可能性があります。

ケース1 ジェネリックを使用しているクラスをクラスが拡張している場合

public class TestGenerics {
    public static void main(String[] args) {
        Type type = TestMySuperGenericType.class.getGenericSuperclass();
        Type[] gTypes = ((ParameterizedType)type).getActualTypeArguments();
        for(Type gType : gTypes){
            System.out.println("Generic type:"+gType.toString());
        }
    }
}

class GenericClass<T> {
    public void print(T obj){};
}

class TestMySuperGenericType extends GenericClass<Integer> {
}

ケース2 クラスがジェネリックスを使用しているインターフェースを実装している場合

public class TestGenerics {
    public static void main(String[] args) {
        Type[] interfaces = TestMySuperGenericType.class.getGenericInterfaces();
        for(Type type : interfaces){
            Type[] gTypes = ((ParameterizedType)type).getActualTypeArguments();
            for(Type gType : gTypes){
                System.out.println("Generic type:"+gType.toString());
            }
        }
    }
}

interface GenericClass<T> {
    public void print(T obj);
}

class TestMySuperGenericType implements GenericClass<Integer> {
    public void print(Integer obj){}
}

ケース3 インターフェースがGenericsを使用しているインターフェースを拡張している場合

public class TestGenerics {
    public static void main(String[] args) {
        Type[] interfaces = TestMySuperGenericType.class.getGenericInterfaces();
        for(Type type : interfaces){
            Type[] gTypes = ((ParameterizedType)type).getActualTypeArguments();
            for(Type gType : gTypes){
                System.out.println("Generic type:"+gType.toString());
            }
        }
    }
}

interface GenericClass<T> {
    public void print(T obj);
}

interface TestMySuperGenericType extends GenericClass<Integer> {
}

1

それは非常に簡単です。同じクラス内から必要な場合:

Class clazz = this.getClass();
ParameterizedType parameterizedType = (ParameterizedType) clazz.getGenericSuperclass();
try {
        Class typeClass = Class.forName( parameterizedType.getActualTypeArguments()[0].getTypeName() );
        // You have the instance of type 'T' in typeClass variable

        System.out.println( "Class instance name: "+  typeClass.getName() );
    } catch (ClassNotFoundException e) {
        System.out.println( "ClassNotFound!! Something wrong! "+ e.getMessage() );
    }

0

実際、T型のクラスにフィールドがあると思います。T型のフィールドがない場合、ジェネリックTypeを持つことの意味は何ですか?したがって、そのフィールドでinstanceofを実行するだけです。

私の場合、私は

リスト<T>アイテム;
私のクラスで、私はクラスのタイプが「地方」であるかどうかを確認します

if(items.get(0)instanceof Locality)...

もちろん、これは、可能なクラスの総数が制限されている場合にのみ機能します。


4
items.isEmpty()がtrueの場合はどうすればよいですか?
chaotic3quilibrium 2013年

0

この質問は古いですが、今はグーグルを使用するのが最善です Gson

カスタムを取得する例viewModel

Class<CustomViewModel<String>> clazz = new GenericClass<CustomViewModel<String>>().getRawType();
CustomViewModel<String> viewModel = viewModelProvider.get(clazz);

ジェネリック型クラス

class GenericClass<T>(private val rawType: Class<*>) {

    constructor():this(`$Gson$Types`.getRawType(object : TypeToken<T>() {}.getType()))

    fun getRawType(): Class<T> {
        return rawType as Class<T>
    }
}

0

Genericsを利用するメソッドにT.classを渡したかった

readFileメソッドは、fileNameで指定された.csvファイルをフルパスで読み取ります。異なる内容のcsvファイルが存在する可能性があるため、適切なオブジェクトを取得できるようにモデルファイルクラスを渡す必要があります。これはcsvファイルを読み込んでいるので、私は一般的な方法でやりたかったです。なんらかの理由で、上記の解決策のどれも私にとってうまくいきませんでした。Class<? extends T> typeそれを機能させるために使用する必要があります 。CSVファイルの解析にはopencsvライブラリを使用しています。

private <T>List<T> readFile(String fileName, Class<? extends T> type) {

    List<T> dataList = new ArrayList<T>();
    try {
        File file = new File(fileName);

        Reader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file)));
        Reader headerReader = new BufferedReader(new InputStreamReader(new FileInputStream(file)));

        CSVReader csvReader = new CSVReader(headerReader);
        // create csv bean reader
        CsvToBean<T> csvToBean = new CsvToBeanBuilder(reader)
                .withType(type)
                .withIgnoreLeadingWhiteSpace(true)
                .build();

        dataList = csvToBean.parse();
    }
    catch (Exception ex) {
        logger.error("Error: ", ex);
    }

    return dataList;
}

これはreadFileメソッドが呼び出される方法です

List<RigSurfaceCSV> rigSurfaceCSVDataList = readSurfaceFile(surfaceFileName, RigSurfaceCSV.class);

-4

私はこれに回避策を使用しています:

class MyClass extends Foo<T> {
....
}

MyClass myClassInstance = MyClass.class.newInstance();
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.