web-dev-qa-db-ja.com

ジェネリック型Tのクラスインスタンスを取得する方法

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

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

610
robinmag

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

これに対する一般的な解決策は、typeパラメータの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
    }
}
507
Zsolt Török

クラスパスに追加の依存関係を追加せずに自分でこれを行う方法を探していました。いくつかの調査の結果、私はそれがisが一般的なスーパータイプを持っている限り可能であることを知りました。私は _ dao _ レイヤをジェネリックレイヤスーパータイプで扱っていたので、これは私には問題ありませんでした。これがあなたのシナリオに合うならば、それが私たちの最も新しいアプローチです。

私が遭遇したほとんどのジェネリックのユースケースはある種の一般的なスーパータイプを持っています。 List<T>の場合はArrayList<T>GenericDAO<T>の場合はDAO<T>など.

純粋なJavaソリューション

記事{実行時にJavaで総称型にアクセスするでは、純粋なJavaを使用してそれを実行する方法について説明しています。

春のソリューション

私のプロジェクトは 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 ";
    }
195
Ben Thurley

ただし、小さな抜け穴があります。Fooクラスをabstractとして定義した場合。それはあなたがあなたのクラスを次のようにインスタンス化しなければならないことを意味するでしょう:

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 );
    }
}
87
bert bruynooghe

標準的なアプローチ/回避策/解決策は、次のようにコンストラクタに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();
    }
 }
34
Andreas_D

一般的な抽象スーパークラスがあるとします。

public abstract class Foo<? extends T> {}

そして、Tを拡張する一般的なBarを使ってFooを拡張する2番目のクラスがあります。

public class Second extends Foo<Bar> {}

FooクラスのクラスBar.classを取得するには、(bert bruynooghe answerから)Typeを選択し、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です。

17
droidpl

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

@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>() {};
13
Yaroslav Kovbas

タイプ消去のためにそれはできません。スタックオーバーフローの質問も参照してくださいJavaジェネリックス - 型消去 - いつ、何が起こるか

10
Konrad Garus

私は抽象ジェネリッククラスでこの問題を抱えていました。この特定のケースでは、解決策はより単純です。

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

そして後に派生クラスでは:

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

他の人が提案したClassよりも優れた方法は、Classを使って行ったことを実行できるオブジェクトを渡すことです。たとえば、新しいインスタンスを作成します。

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>());
8
Ricky Clarkson

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

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 __()
    {
    }
}
5
unknown6656

それが可能だ:

class Foo<T> {
  Class<T> clazz = (Class<T>) DAOUtil.getTypeArguments(Foo.class, this.getClass()).get(0);
}

svn/trunk/dao/src/main/Java/com/googlecode/genericdao/dao/DAOUtil.Java から2つの関数が必要です。

詳細については、ジェネリックの反映を参照してください。

4
Peter Tseng

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

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];
    }
}
3
Juan Urrego

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

したがって、クラスを抽象化すると、クラスを拡張することになり、サブクラス化の要件が満たされます。 (lombokの@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
Riaan Schutte
   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
Abel ANEIROS

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

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 あなたのクラスが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);
}

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
Anil Agrawal

この質問は古いですが、今ではgoogle 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
Vahe Gharibyan

実際には、私はあなたがタイプTのあなたのクラスにフィールドを持っていると仮定します。タイプTのフィールドがなければ、ジェネリックTypeを持つことのポイントは何ですか?だから、あなたは単にそのフィールドにinstanceofをすることができます。

私の場合は、

<T>項目をリストします。
if(items.get(0)instanceofローカリティ)... 

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

0
Christine