【问题标题】:How do I get a class instance of generic type T?如何获得泛型类型 T 的类实例?
【发布时间】:2011-03-27 03:48:03
【问题描述】:

我有一个泛型类Foo<T>。在Foo的一个方法中,我想得到T类型的类实例,但是我就是不能调用T.class

使用T.class 绕过它的首选方法是什么?

【问题讨论】:

标签: java generics


【解决方案1】:

简短的回答是,没有办法找出 Java 中泛型类型参数的运行时类型。我建议阅读Java Tutorial 中有关类型擦除的章节以了解更多详细信息。

一种流行的解决方案是将类型参数的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
    }
}

【讨论】:

  • 这个答案确实提供了一个有效的解决方案,但说在运行时无法找到泛型类型是不准确的。事实证明,类型擦除比一揽子擦除要复杂得多。我的回答向您展示了如何获取类泛型类型。
  • @BenThurley 巧妙的技巧,但据我所知,它只有在有通用超类型可供使用时才有效。在我的示例中,您无法在 Foo. 中检索 T 的类型
  • @webjockey 不,你不应该。在构造函数中分配没有默认分配的typeParameterClass 非常好。无需再次设置。
  • 这是第一个想到的解决方案,但有时不是您将创建/启动对象。所以你将无法使用构造函数。例如同时从数据库中检索 JPA 实体。
  • @ZsoltTörök 我想我找到了解决这个问题的方法。请检查我的回答:stackoverflow.com/a/64504193/9432967
【解决方案2】:

我一直在寻找一种方法来自己执行此操作,而无需向类路径添加额外的依赖项。经过一番调查,我发现只要你有一个通用的超类型就可以了。这对我来说没问题,因为我正在使用具有通用层超类型的 DAO 层。如果这符合您的情况,那么恕我直言,这是最巧妙的方法。

我遇到的大多数泛型用例都有某种泛型超类型,例如List&lt;T&gt;ArrayList&lt;T&gt;GenericDAO&lt;T&gt;DAO&lt;T&gt;

纯Java解决方案

文章 Accessing generic types at runtime in 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 ";
    }

完整代码示例

有些人在 cmets 中苦苦挣扎以使其正常工作,因此我编写了一个小应用程序来展示这两种方法的实际效果。 https://github.com/benthurley82/generic-type-resolver-test

【讨论】:

  • 请澄清 resolveTypeArgument 参数的含义
  • getClass() 是 java.lang.Object 的一个方法,它将在运行时返回特定对象的类,这是您要为其解析类型的对象。 AbstractHibernateDao.class 只是泛型类型类层次结构的基类或超类的名称。包含导入语句,因此您应该能够轻松找到文档并自行检查。这是页面docs.spring.io/spring/docs/current/javadoc-api/org/…
  • “纯Java解决方案”中的链接坏了,现在是blog.xebia.com/acessing-generic-types-at-runtime-in-java
  • @AlikElzin-kilaka 它在构造函数中使用 Spring 类 GenericTypeResolver 进行初始化。
  • java.lang.ClassCastException: java.lang.Class 不能转换为 java.lang.reflect.ParameterizedType
【解决方案3】:

但是有一个小漏洞:如果您将Foo 类定义为抽象类。 这意味着您必须将您的类实例化为:

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

(注意末尾的双括号。)

现在您可以在运行时检索T 的类型:

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

但请注意,mySuperclass 必须是实际定义 T 的最终类型的类定义的超类。

它也不是很优雅,但你必须决定在你的代码中是喜欢new Foo&lt;MyType&gt;(){}还是new Foo&lt;MyType&gt;(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 );
    }
}

【讨论】:

  • 这很容易成为这里的最佳答案!此外,对于它的价值,这是 Google Guice 用于将类与 TypeLiteral 绑定的策略
  • 注意,每次使用这种对象构造方法,都会创建一个新的匿名类。换句话说,以这种方式创建的两个对象ab 都将扩展同一个类,但没有相同的实例类。 a.getClass() != b.getClass()
  • 在一种情况下这不起作用。如果 Foo 应该实现一个接口,例如 Serializable,那么匿名类将不是 Serializable,除非实例化的类是。我试图通过创建一个可序列化的工厂类来解决它,该工厂类创建从 Foo 派生的匿名类,但随后,由于某种原因,getActualTypeArguments 返回泛型类型而不是实际类。例如:(new FooFactory()).createFoo()
【解决方案4】:

标准方法/解决方法/解决方案是将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();
    }
 }

【讨论】:

  • 但是在实际使用中好像不能@autowired,有什么办法解决吗?
  • @AlfredHuang 解决方法是为执行此操作的类创建一个 bean,而不依赖于自动装配。
【解决方案5】:

假设您有一个通用的抽象超类:

public abstract class Foo<? extends T> {}

然后你有第二个类,它使用扩展 T 的通用 Bar 扩展 Foo:

public class Second extends Foo<Bar> {}

您可以通过选择Type(来自bert bruynooghe 的答案)并使用Class 实例推断它来获取Foo 类中的Bar.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);

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

【讨论】:

  • toString().split(" ")[1] 是问题所在,避免使用"class "
【解决方案6】:

这是一个可行的解决方案:

@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&lt;Integer&gt;)

  1. 必须创建为匿名实现 (new Generic&lt;Integer&gt;() {};)

【讨论】:

  • getTypeName 调用toString,因此可以替换为.getActualTypeArguments()[0].toString();
【解决方案7】:

我在抽象泛型类中遇到了这个问题。在这种特殊情况下,解决方案更简单:

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

以及后来的派生类:

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

【讨论】:

  • 是的,但我想在扩展此类时保留非常少的内容。检查droidpl的答案
【解决方案8】:

与大多数答案相反,有可能(没有外部库!)

以下是我对这个问题的(丑陋但有效的)解决方案:

import java.lang.reflect.TypeVariable;


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

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

// Generic helper class which (only) provides type information. This avoids
// the usage of a local variable of type T, which would have to be initialized.
private final class __<T> {
    private __() {
    }
}

【讨论】:

    【解决方案9】:

    由于类型擦除,您无法执行此操作。另请参阅 Stack Overflow 问题 Java generics - type erasure - when and what happens

    【讨论】:

      【解决方案10】:

      比其他人建议的 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:我看不出这家工厂应该如何返回参数化的 foo。你能解释一下你是如何摆脱 Foo 的吗?在我看来,这只给出了未参数化的 Foo。 make10 中的 T 不就是这里的 Foo 吗?
      • @ib84 我已经修复了代码;当我最初写答案时,我似乎错过了 Foo 被参数化。
      【解决方案11】:

      有可能:

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

      您需要来自hibernate-generic-dao/blob/master/dao/src/main/java/com/googlecode/genericdao/dao/DAOUtil.java 的两个函数。

      更多解释请见Reflecting generics

      【讨论】:

      • 这个解决方案似乎需要一个通用的超类,就像这里的其他解决方案一样。
      【解决方案12】:

      我假设,既然你有一个泛型类,你就会有一个像这样的变量:

      private T t;
      

      (这个变量需要在构造函数中取值)

      在这种情况下,您可以简单地创建以下方法:

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

      希望对你有帮助!

      【讨论】:

        【解决方案13】:

        我找到了一种通用且简单的方法来做到这一点。在我的类中,我创建了一个根据泛型类型在类定义中的位置返回泛型类型的方法。让我们假设一个这样的类定义:

        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];
            }
        }
        

        【讨论】:

          【解决方案14】:

          这很简单。 如果您需要来自同一个班级:

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

          【讨论】:

            【解决方案15】:

            很多人不知道这个技巧!其实我今天才发现!它像梦一样工作!看看这个例子:

            public static void main(String[] args) {
                Date d=new Date();  //Or anything you want!
                printMethods(d);
            }
            
            public static <T> void printMethods(T t){
                Class<T> clazz= (Class<T>) t.getClass(); // There you go!
                for ( Method m : clazz.getMethods()){
                    System.out.println( m.getName() );
                }
            }
            

            【讨论】:

            • 这段代码真正解决了哪个问题?将方法声明更改为public static void printMethods(Object t),它将完全一样。这个答案没有“技巧”,只是一个过时的类型参数。
            • @Holger 如果用户想要获取对象的构造函数并调用它们来创建该类型的新对象怎么办?
            • 其实你可以在clazz上拨打getConstructures()来做。现在的问题是,为什么 OP 想要获得 T.class ,而他只能做到 t.getClass()
            • 我问你的代码试图解决什么问题。询问“如果用户想要答案中未显示的其他内容怎么办”不是答案。是的,您可以在 Class 对象上调用 getConstructors。 1) 但你的代码没有 2) 但它仍然不能证明使用类型参数是合理的。 t.getClass()的结果是Class&lt;? extends Object&gt;,不管你声明参数是T还是Object。这没什么区别。您的代码中有一个 unchecked type cast (Class&lt;T&gt;)。这并不比使用getClass().getConstructor().newInstance() 将结果转换为您想要的任何内容更好。
            【解决方案16】:
               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
                    }
               }
            

            【讨论】:

              【解决方案17】:

              如果您正在扩展或实现任何使用泛型的类/接口,您可以获得父类/接口的通用类型,而无需修改任何现有的类/接口。

              可能有三种可能,

              案例 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> {
              }
              

              【讨论】:

                【解决方案18】:

                正如其他答案中所解释的,要使用这种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();
                

                【讨论】:

                  【解决方案19】:

                  实际上,我想你的类中有一个类型为 T 的字段。如果没有类型为 T 的字段,那么拥有一个泛型类型有什么意义?所以,你可以简单地在那个字段上做一个 instanceof。

                  就我而言,我有一个

                  List items;
                  在我的班级中,我通过以下方式检查班级类型是否为“Locality”
                  if (items.get(0) instanceof Locality) ...
                  

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

                  【讨论】:

                  • 如果 items.isEmpty() 为真怎么办?
                  【解决方案20】:

                  这个问题很老了,但现在最好是使用谷歌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>
                      }
                  }
                  

                  【讨论】:

                    【解决方案21】:

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

                    readFile 方法使用完整路径读取由 fileName 指定的 .csv 文件。可能存在具有不同内容的 csv 文件,因此我需要传递模型文件类,以便我可以获得适当的对象。由于这是读取 csv 文件,因此我想以通用方式进行操作。出于某种原因或其他原因,上述解决方案都不适合我。我需要使用 Class&lt;? extends T&gt; 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);
                    

                    【讨论】:

                      【解决方案22】:

                      我正在为此使用解决方法:

                      class MyClass extends Foo<T> {
                      ....
                      }
                      
                      MyClass myClassInstance = MyClass.class.newInstance();
                      

                      【讨论】:

                        猜你喜欢
                        • 1970-01-01
                        • 2019-01-20
                        • 1970-01-01
                        • 1970-01-01
                        • 1970-01-01
                        • 1970-01-01
                        • 1970-01-01
                        相关资源
                        最近更新 更多