泛型 Java - 获取泛型的泛型类型

Generics Java - Get type of generics of generics

我怎样才能得到这个 class 的类型? 对于上下文,我正在使用 modelMapper 并且我需要 class 类型 T 以从 S 转换为 T。

@Component
public class A<T, S> {
    
    private Class<T> tType;
    private Class<S> sType;
    
    @SuppressWarnings("unchecked")
    public A() {
        this.tType = (Class<T>) // some method to take the type
        this.sType = (Class<S>) // some method to take the type
    }   

    public T toModel(S regras) {
        return modelMapper.map(regras, tType); 
    }

上下文:

public class B{

    @Autowired 
    private A<SomeClassX, SomeClassY> var;
    
    // Some code

我已经尝试了 N 种方式,我把“// some method to take the type”放在了那里,但没有任何效果。例如:

    @SuppressWarnings("unchecked")
    public A() {
        this.tType = (Class<T>) getGenericClassType(0);
        this.sType = (Class<S>) getGenericClassType(1);
    }   

    private Type getGenericClassType(int index) {
        Type type = getClass().getGenericSuperclass(); 
        while(!(type instanceof ParameterizedType)) { 
            if (type == null) { 
                return null; 
            }
            
            if (type instanceof ParameterizedType) {
                type = ((Class<?>) ((ParameterizedType) type).getRawType()).getGenericSuperclass();
            } else {
                type = ((Class<?>) type).getGenericSuperclass();
            } 
        }
        
        return ((ParameterizedType) type).getActualTypeArguments()[index];
    }

    @SuppressWarnings("unchecked")
    public A() {
        this.tType = (Class<T>) GenericTypeResolver.resolveTypeArguments(getClass(), GenericConverter.class)[0];
        this.sType = (Class<S>) GenericTypeResolver.resolveTypeArguments(getClass(), GenericConverter.class)[1];
    }   

你不能。泛型是编译器想象力的产物:编译器使用它们来生成错误并注入强制转换,然后将其全部删除。它不在 class 文件中,因此在运行时不可用,因此,'what is my T actually' 的概念是 不可能的

有解决方法:

java.lang.Class

您当然可以将 j.l.Class 实例传递给构造函数。您的代码段使用名称 'A' 和 'B' 非常不幸,因为 single-caps 通常用于类型变量,所以我将其命名为 MyCompA 和 MyCombB。

类似于:

public class MyCompA<S, T> {
  private final Class<S> sourceType;
  private final Class<T> targetType;

  public MyCompA(Class<S> sourceType, Class<T> targetType) {
    this.sourceType = sourceType;
    this.targetType = targetType;
  }

  public T toModel(S regras) {
      return modelMapper.map(regras, targetType); 
  }
}

尽管如此,听起来您实际上并不需要 sourceType 任何东西,所以您最好不要使用它。

这个策略的问题是two-fold:

  • j.l.Class 实例可以表示泛型无法表示的东西。具体来说,原语:int,原语,有一个代表它的j.l.Class实例;您可以使用表达式 int.class 获取它。 int 不是有效的泛型(无论如何,可能在未来的 java 版本中):Class<int> 不起作用,List<int>.[=71 也不起作用=]
  • 一个j.l.Class实例只能代表基本类型——即被擦除的。 List<String> 不能 表示为 j.l.Class 实例。这意味着 S 和 T 类型 本身不能有泛型 这通常是一个破坏因素 - 你可能希望能够将某些东西映射到 List<String>.

超类型代币

一种出路是 STT。他们看起来很时髦。制作它们:

TypeToken<List<String>> = new TypeToken<>() {};

注意那里奇怪的尾随 {}。这创建了一个匿名 class 和 classes do 携带泛型信息,这些信息在编译后仍然存在,因此可以通过反射访问进行查询。运行时系统不会对它做任何事情 - 就 JVM 而言,它是注释(JVM 不知道什么是泛型,也不会将它们用于任何事情。它在 class 文件中是为了方便仅 javac ....但我们可以通过思考阅读它,所以就是这样)。

public abstract class TypeToken<T> {
  public Type getType() {
    Type t = getClass().getGenericSuperclass();
    if (!(t instanceof ParameterizedType pt)) throw new IllegalStateException("Incorrect use of TypeToken: " + t);
    
    Type[] typeArgs = pt.getActualTypeArguments();
    if (typeArgs.length == 1) return typeArgs;
    throw new IllegalStateException("Incorrect use of TypeToken: " + t);
}

我已经向您展示了如何创建这些实例。如果需要,您可以在构造函数中包含对 getType() 的调用(并且您可能希望缓存 return 值;它不能更改并且反射可能有点昂贵),因为那样当您制作无效的类型标记时,您会得到异常(例如:new TypeToken() {} - 这是原始用法,当然在这里不起作用,重点是捕获 <> 中的内容)。

API麻烦!

您的 modelMapper.map 方法显然需要 Class<> 类型的值。如前所述,那些 不能 代表泛型,所以这个映射器只能映射到基本 typeargs-less 类型。在这种情况下,STT 完全矫枉过正,您只需要传递 class 个实例的简单解决方案。