如何获得通用类型T的类实例?


699

我有一个泛型类Foo<T>。在一种方法中Foo,我想获取type的类实例T,但是我无法调用T.class

使用它解决问题的首选方法是什么T.class


2
试试这个问题的答案。我认为它是相似的。stackoverflow.com/questions/1942644/...
周华健



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

Answers:


567

简短的答案是,无法找到Java中泛型类型参数的运行时类型。我建议阅读Java教程中有关类型擦除的章节以获取更多详细信息。

一个流行的解决方案是Class将type参数的传递给泛型类型的构造函数,例如

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,2012年

3
@BenThurley整洁的技巧,但是据我所知,它只有在有通用的超类型可供使用时才有效。在我的示例中,您无法在Foo <T>中检索T的类型。
ZsoltTörök2013年

@webjockey不,你不应该。typeParameterClass在构造函数中分配没有默认分配的完全可以。无需再次设置。
Adowrath

这是想到的第一个解决方案,但有时创建/启动对象的不是您自己。因此,您将无法使用构造函数。例如,当从数据库中检索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只是泛型类型类层次结构的基类或超类的名称。包含import语句,因此您应该可以轻松找到文档并进行检查。这是docs.spring.io/spring/docs/current/javadoc-api/org/…
Ben

什么是4.3.6及更高版本的spring解决方案。在Spring 4.3.6中不起作用。
艾伦(Erlan)

1
“纯Java解决方案”中的链接已断开,现在为blog.xebia.com/acessing-generic-types-at-runtime-in-java
Nick Breen

1
@ AlikElzin-kilaka可以使用Spring类GenericTypeResolver在构造函数中初始化。
本·瑟利

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用于与-ATG绑定类的策略TypeLiteral
2014年

14
请注意,每次使用这种对象构造方法时,都会创建一个新的匿名类。换句话说,两个对象ab创建这种方式都将扩展同一类,但不具有相同的实例类。 a.getClass() != b.getClass()
马丁·塞拉诺

3
在一种情况下,这不起作用。如果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:

public class Second extends Foo<Bar> {}

您可以Bar.class通过选择Type(来自bert bruynooghe答案)并使用Class实例进行推断来获得Foo 类中的类:

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

您必须注意,此操作并不理想,因此最好缓存计算的值,以避免对此进行多次计算。典型用途之一是在通用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

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 '16


9

一个比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>());

// @ Ricky Clarkson:我不知道这个工厂应该如何返回参数化的foos。您能否解释一下如何从中获得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

如果要扩展或实现任何使用泛型的类/接口,则可以获取父类/接口的泛型类型,而无需修改任何现有的类/接口。

可能有三种可能性,

情况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 当您的接口扩展使用泛型的接口时

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的字段,那么拥有通用类型有什么意义呢?因此,您只需在该字段上执行instanceof。

就我而言,我有一个

列出<T>项;
在我的班级中,然后检查班级类型是否为“本地化”

如果(items.get(0)instanceof Locality)...

当然,这仅在可能的类别总数有限的情况下有效。


4
如果items.isEmpty()为true,该怎么办?
chaotic3quilibrium

0

这个问题很旧,但现在最好是使用google Gson

获取custom的示例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

我想将T.class传递给使用泛型的方法

readFile方法读取具有全路径的fileName指定的.csv文件。可能存在具有不同内容的csv文件,因此我需要传递模型文件类,以便可以获取适当的对象。因为这是读取csv文件,所以我想以一种通用的方式来做。由于某种原因或其他原因,以上解决方案均不适用于我。我需要使用 Class<? extends T> type它来使其工作。我使用opencsv库来解析CSV文件。

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();
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.