Home > database >  Generics Java - Get type of generics of generics
Generics Java - Get type of generics of generics

Time:05-29

How can I get the type of this class? For context, I'm using modelMapper and I need the class type T to convert from S to 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); 
    }

Context:

public class B{

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

I already try N-ways where I put the "// some method to take the type", but nothings works. For instance:

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

Or

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

CodePudding user response:

You cannot. Generics are a figment of the compiler's imagination: The compiler uses them to generate errors and inject casts, and then deletes it all. It isn't in the classfile, therefore isn't available at runtime, therefore, the notion of 'what is my T actually' is impossible.

There are workarounds:

java.lang.Class

You can of course pass a j.l.Class instance to the constructor. Your snippet uses the names 'A' and 'B' which is highly unfortunate, as single-caps are usually used for type vars, so I'll name that MyCompA and MyCombB instead.

Something like:

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

Although, it sounds like you do'nt actually need sourceType for anything, so you might as well leave that off.

The problem with this strategy is two-fold:

  • There are things a j.l.Class instance can represent that generics cannot represent. Specifically, the primitives: int, the primitive, has a j.l.Class instance that represents it; you can obtain it with the expression int.class. int is not valid generics (not yet, at any rate, maybe in a future java version): Class<int> doesn't work, nor does List<int>.
  • A j.l.Class instance can only represent BASIC TYPES - i.e. erased ones. List<String> cannot be represented as a j.l.Class instance. That means that the S and T types cannot have generics themselves which is usually a dealbreaker - you presumably want the ability to, say, map something to a List<String>.

super type tokens

One way out is STTs. They look funky. To make them:

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

Note the weird trailing {} there. That creates an anonymous class and classes do carry generics info that survives compilation and can therefore be queried with reflective access. The runtime system doesn't do anything with it - it's comments as far as the JVM is concerned (the JVM does not know what generics are and doesn't use them for anything. It's there in class files for the benefit of javac only.... but we can read it with reflection, so there's that).

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

I've shown you how to make instances of these. If you want, you can include in the constructor a call to getType() (and you might want to cache the return value; it can't change and reflection can be a bit pricey), because that way you will get the exception as you make invalid type tokens (example: new TypeToken() {} - that's raw usage, which doesn't work here of course, the whole point is to capture the stuff in the <>).

API trouble!

Your modelMapper.map method requires, evidently, a value of type Class<>. As covered before, those cannot represent generics, so this mapper thing can only map to base, typeargs-less types. In which case, the STTs are total overkill and you just want the simple solution of passing in the class instances.

  • Related