2

I am tying to save the Type of a generic value, because i can't get it at runtime:

public class A<T> {
    private final Class<T> genericType;
    public A(Class<T> genericType) {
        this.genericType = genericType;
    }

    public Class getGenericType() {
        return genericType;
    }
}

To make subclasses now, I use it as follows:

public class B extends A<String> {
    public B() {
        super(String.class);
    }
}

Note thet the super()'s parameter type matches (by compile timne check) to the A's generic type. That works fine. But if i want to have it with a Map, i cannot get the correct class object:

public class C extends A<Map<String, String>> {
    public C() {
        super(Map.class); // does not match Map<String,String>
        super(Map<String,String>.class) // no valid java expression, i dont know what 
    }
}

Sooo anyone got a tip to help me out of this misery? Best i could do currently, is to give up the strong typing in A:

public class A<T> {
    // old: private final Class<T> genericType;
    private final Class genericType;  // note the missing generic
    public A(Class genericType) {     // here as well
        this.genericType = genericType;
    }

    public Class getGenericType() {
        return genericType;
    }
}

markus
  • 511
  • 1
  • 4
  • 15
  • 1
    Generally it's tricky to pass generic types around like that. Maybe take a look at Jackson for inspiration, wherein you anonymously extend TypeReference with your actual typing, which allows retaining type information – Coderino Javarino Dec 06 '19 at 10:06
  • 1
    Guava uses the same pattern for `TypeToken`, which is intended to be used in general code. – chrylis -cautiouslyoptimistic- Dec 06 '19 at 10:08
  • thx for your suggestions :) so far, it seems like a rabbit hole that i wanted to avoid for my (except this case) simple code. Guava and Jackson seem to have a quite similar way of dealing with it. – markus Dec 06 '19 at 11:23

2 Answers2

2

I am not sure whether this satisfied your requirement, but you can do something similar as bellow, see How to get the class of a field of type T?

import java.lang.reflect.*;
import java.util.*;


public class GenericTypeTest{

     public static void main(String []args){
        B b = new B();
        System.out.println("B is a " + b.getGenericType());
        C c = new C();
        System.out.println("C is a " + c.getGenericType());
     }
}


class A<T> {
      public Class getGenericType() {
          Object genericType =  ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
           if(genericType instanceof ParameterizedType){
               genericType = ((ParameterizedType)genericType).getRawType();
           }
        return (Class<T>) genericType;
    }
}



class B extends A<String> {
}

class C extends A<Map<String,String>> {
}

this will get output something like

B is a class java.lang.String
C is a interface java.util.Map
Melan
  • 458
  • 2
  • 15
0

There is just one Class object representing Map at runtime, which you can get by evaluating Map.class. There are no separate Class objects representing Map<String, String> or Map<Integer, Integer> or whatever at runtime. If you just want to take Map.class, which normally has type Class<Map>, and force that into Class<Map<String, String>>, you could do that via some unchecked casts:

super((Class<Map<String, String>>)(Class<?>)Map.class);

But whether that would do what you want depends on what you expect to do with your variable of type Class<T>, genericType, which you have not shown. For example, if you will use its .isInstance() method to check whether an object is an instance of T at runtime, know that since we don't know the generic type arguments of objects at runtime, we will only be able to check the raw type of the object, and not its type argument. This is the same reason why you can do foo instanceof Map or foo instanceof Map<?> but not foo instanceof Map<String, String>.

Or, maybe you want to use its .cast() method to do a type check on an object at runtime, which normally throws an exception if the object is not an instance of the Class's class, and if it is, it returns the object, but with compile-time type T. But here, again, you can't check at runtime whether an object is an instance of a parameterized type like Map<String, String>; you can only check whether it is a Map. So by it allowing you to get the result as type T without warning might be unsafe, as you get a compile-time expression of type Map<String, String>, but it might really be a Map<Integer, Integer>. This is the same reason why an unchecked cast like (Map<String, String>)foo causes an unchecked cast warning. It is unsafe and might cause unexpected problems elsewhere in your code.

newacct
  • 119,665
  • 29
  • 163
  • 224