如何获取泛型类型T的类实例

rob*_*mag 659 java generics

我有一个泛型课,Foo<T>.在一个方法中Foo,我想得到类型为T的类实例,但我无法调用T.class.

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

Zso*_*rök 534

简而言之,没有办法在Java中找出泛型类型参数的运行时类型.我建议阅读Java Tutorial中关于类型擦除的章节以获取更多详细信息.

一种流行的解决方案是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
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 这个答案确实提供了一个有效的解决方案,但是说在运行时无法找到泛型类型是不准确的.事实证明,类型擦除远比毯式擦除复杂得多.我的回答向您展示了如何获得类泛型类型. (70认同)
  • @BenThurley整洁的技巧,但据我所知,只有有一个通用的超类型才能使用它.在我的示例中,您无法在Foo <T>中检索T的类型. (3认同)

Ben*_*ley 212

我一直在寻找一种方法来自己做这个,而不需要在类路径上添加额外的依赖.经过一番调查,我发现,这可能的,只要你有一个通用的超类型.这对我来说没问题,因为我正在使用带有通用图层超类型的DAO图层.如果这适合您的情况,那么这是最好的方法恕我直言.

我遇到的大多数泛型用例都有某种通用的超类型,例如List<T>for ArrayList<T>GenericDAO<T>for DAO<T>等.

纯Java解决方案

文章在Java中以运行时访问泛型类型说明了如何使用纯Java来实现它.

Spring解决方案

我的项目使用Spring,因为Spring有一个方便的实用方法来查找类型,因此更好.这对我来说是最好的方法,因为它看起来最好.我想如果你没有使用Spring,你可以编写自己的实用工具方法.

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

  • java.lang.ClassCastException:java.lang.Class无法转换为java.lang.reflect.ParameterizedType (6认同)
  • @BenThurley我只是深入了解为什么你的代码对我不起作用。不幸的是,答案是它根本不起作用:您的代码从类的*声明*中获取类型参数,而不是实例化泛型类的类型。在您的 github 示例中, `Foo` 扩展了 `AbstractFoo&lt;GenericType&gt;`,其中 `GenericType` 是您也定义的固定类,因此在本例中,`Foo` 的泛型类型参数可以从 *声明* 中读取。但如果“GenericType”是一个类型参数,那么这将不起作用。 (3认同)

ber*_*ghe 94

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

Foo<MyType> myFoo = new Foo<MyType>(){};
Run Code Online (Sandbox Code Playgroud)

(注意最后的双括号.)

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

Type mySuperclass = myFoo.getClass().getGenericSuperclass();
Type tType = ((ParameterizedType)mySuperclass).getActualTypeArguments()[0];
Run Code Online (Sandbox Code Playgroud)

但请注意,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;
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

然后:

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 );
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 请注意,每次使用此对象构造方法时,都会创建一个新的匿名类.换句话说,以这种方式创建的两个对象`a`和`b`都将扩展相同的类但不具有相同的实例类.`a.getClass()!= b.getClass()` (13认同)
  • 这是这里最好的答案!此外,对于它的价值,这是Google Guice用于使用`TypeLiteral`绑定类的策略 (5认同)
  • 有一种情况不适用.如果Foo应该实现一个接口,比如Serializable,那么除非实例化类,否则匿名类不会是Serializable.我试图通过创建一个可序列化的工厂类来解决它,它创建了从Foo派生的匿名类,但是由于某种原因,getActualTypeArguments返回泛型类型而不是实际的类.例如:__(new FooFactory <MyType>()).createFoo()__ (3认同)

And*_*s_D 36

标准方法/变通方法/解决方案是将一个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();
    }
 }
Run Code Online (Sandbox Code Playgroud)

  • 但在实际使用中似乎不能@autowired,有什么办法可以解决吗? (2认同)

dro*_*dpl 19

想象一下,你有一个通用的抽象超类:

public abstract class Foo<? extends T> {}
Run Code Online (Sandbox Code Playgroud)

然后你有第二个类扩展了Foo,扩展了T:

public class Second extends Foo<Bar> {}
Run Code Online (Sandbox Code Playgroud)

您可以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);
Run Code Online (Sandbox Code Playgroud)

您必须注意此操作并不理想,因此最好缓存计算值以避免对此进行多次计算.其中一个典型用途是通用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;
    }
}
Run Code Online (Sandbox Code Playgroud)

从其他函数中的Foo类或Bar类调用时,返回的值为Bar.class.


Yar*_*bas 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 <> ");
    }
} 
Run Code Online (Sandbox Code Playgroud)

注意: 只能用作超类

  1. 必须扩展类型类(Child extends Generic<Integer>)

要么

  1. 必须创建为匿名实现(new Generic<Integer>() {};)

  • getTypeName调用toString,因此可以替换为.getActualTypeArguments()[0] .toString(); (3认同)

Kon*_*rus 10

由于类型擦除,你不能这样做.另请参阅Stack Overflow问题Java泛型 - 类型擦除 - 何时以及发生了什么.


use*_*664 9

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

abstract class Foo<T> {
    abstract Class<T> getTClass();
    //...
}
Run Code Online (Sandbox Code Playgroud)

以及后来的派生类:

class Bar extends Foo<Whatever> {
    @Override
    Class<T> getTClass() {
        return Whatever.class;
    }
}
Run Code Online (Sandbox Code Playgroud)


Ric*_*son 8

比其他人建议的类更好的路径是传入一个对象,该对象可以执行您对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>());
Run Code Online (Sandbox Code Playgroud)


Pon*_*ios 7

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

private T t;
Run Code Online (Sandbox Code Playgroud)

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

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

Class<T> getClassOfInstance()
{
    return (Class<T>) t.getClass();
}
Run Code Online (Sandbox Code Playgroud)

希望能帮助到你!

  • 仅当 t 不为空时才有效 (3认同)

Pet*_*eng 5

这是可能的:

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

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

有关更多解释,请参阅反映泛型


unk*_*656 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 __()
    {
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 我试图在创建 EnumSet 时使用它,但它似乎不喜欢它 (3认同)
  • 我知道这已经有几年了,但这返回给我一个 `TypeVariableImpl&lt;T&gt;` 而不是 `T` 本身的类类型 (2认同)