2

I have read Get type of a generic parameter in Java with reflection post and it made me wonder how that would be possible. I used the solution that someone posted and using the code

List<Integer> l = new ArrayList<>();
Class actualTypeArguments = (Class) ((ParameterizedType) l.getClass().getGenericSuperclass()).getActualTypeArguments()[0];

This, however does not work for me, resulting in

java.lang.ClassCastException: sun.reflect.generics.reflectiveObjects.TypeVariableImpl cannot be cast to java.lang.Class

If I remove the class cast, the type of the actual argument is E, which is the type definition from List interface.

My question is, therefore, am I doing something wrong here? This behaviour is something I would have expected anyway, since the types are supposed to be erased during compile time, correct?

Community
  • 1
  • 1
Bober02
  • 15,034
  • 31
  • 92
  • 178

3 Answers3

3

The code you use only works in some very specific cases, where the actual type parameter is known (and stored) at compile time.

For example if you did this:

class IntegerList extends ArrayList<Integer> {}

List<Integer> l = new IntegerList();

In this case the code you showed would actually return Integer.class, because Integer is "baked into" the IntegerList.

Some libraries (ab)use this trick via the use of type tokens. See for example the GSON class TypeToken:

Represents a generic type T. You can use this class to get the generic type for a class. > For example, to get the generic type for Collection<Foo>, you can use:

Type typeOfCollectionOfFoo = new TypeToken<Collection<Foo>>(){}.getType()

This works because the anonymous class created in here has compiled-in the information that its type parameter is Collection<Foo>.

Note that this would not work (even if the TypeToken class wouldn't prevent it by making its constructor protected):

Type typeOfCollectionOfFoo = new TypeToken<Collection<Foo>>().getType()
Joachim Sauer
  • 302,674
  • 57
  • 556
  • 614
2

The javadoc will tell you what you are doing.

Class#getGenericSuperclass() states

Returns the Type representing the direct superclass of the entity (class, interface, primitive type or void) represented by this Class.

If the superclass is a parameterized type, the Type object returned must accurately reflect the actual type parameters used in the source code. [...]

The direct superclass of ArrayList is AbstractList. The declaration is as such in the source code

public class ArrayList<E> extends AbstractList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable

So if you print out the Type object returned by it, you will see

java.util.AbstractList<E>

and therefore ParameterizedType#getActualTypeArguments() which states

Returns an array of Type objects representing the actual type arguments to this type.

will return the Type

E

since E is the actual type argument used in the ArrayList class definition.

Sotirios Delimanolis
  • 274,122
  • 60
  • 696
  • 724
1

The method you described does ONLY work, when the Generic Type is Set due to inheritance, because then its known during compile time:

 public class SomeClass<T>{

 }

 public class SpecificClass extends SomeClass<String>{

 }

For this example, you can use the method and you'll get back "String.class".

If you are creating instances on the fly it won't work:

SomeClass s = new SomeClass<String>(); //wont work here.

Some common work around is, to pass the actual class as a parameter for later reference:

 public class SomeClass<T>{
    Class<T> clazz

    public SomeClass(Class<T> clazz){
        this.clazz = clazz;
    }

    public Clazz<T> getGenericClass(){
       return this.clazz;
    } 
 }

usage:

 SomeClass<String> someClass= new SomeClass<String>(String.class);

 System.out.println(someClass.getGenericClass()) //String.class

Actually you don't even need the Generic type for such an scenario, because Java would do the same thing, as if you would handle the "T" as Object. Only advantage is, that you can define getter and Setter of T and don't need to typecast Objects all the time. (Because Java is doing that for you) (It's called Type Erasure)

BenMorel
  • 34,448
  • 50
  • 182
  • 322
dognose
  • 20,360
  • 9
  • 61
  • 107