Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Getting type arguments of parameterized class

I have a class Foo<T, U> with the following constructor:

public Foo() {
  clazz = Class<U>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[1];
}

What I do in the constructor is getting the class of the argument U. I need it because I use it for instantiating that class.

The problem is that it doesn't work when I have a subclass of Foo that isn't a direct sublcass of it. Let me put it with an example.

I have the class Bar<T> extends Foo<T, Class1>. Here, Class1 is not a variable but a class.

I also have the class Baz extends Bar<Class2>. Class2 is a class too, not a variable.

The problem is that it fails when I try to instantiate Baz (Baz -> Bar<Class2> -> Foo<T, Class2>). I get an ArrayIndexOutOfBoundsException because getActualTypeArguments() returns an array containing only the class Class2 (size 1) and I'm trying to get the second element of the array. That's because I'm getting the arguments of Bar<Class2>, instead of the ones of Foo.

What I want is to modify the Foo constructor some way I can get the class in the paramter U, doesn't matter if the class I instantiate is a direct subclass or not. I think I should can go up in the hierarchy of classes until reach the class Foo, cast it as ParameterizedType and get the arguments, but I couldn't find how.

Any idea?

Thanks in advance.

like image 984
drumkey Avatar asked Jun 15 '11 19:06

drumkey


Video Answer


1 Answers

This is a nice approach -- it is the only way to actually get the generic type information compiled within the Java bytecode. Eveything else in terms of generics in Java is just type erasure. See below what appears to be a working solution. There are four scenarios:

  1. A fairly complex inheritance level
  2. Your inheritance scenario
  3. The class itself (clazz will be null)
  4. A subclass that does not provide actual values (clazz = null)

This is the code (Test.java):

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

class A<T1, T2>
{
  Class<T2> clazz;

  A()
  {
    Type sc = getClass().getGenericSuperclass();
    Map<TypeVariable<?>, Class<?>> map = new HashMap<TypeVariable<?>, Class<?>>();
    while (sc != null)
    {
      if (sc instanceof ParameterizedType)
      {
        ParameterizedType pt = (ParameterizedType) sc;
        Type[] ata = pt.getActualTypeArguments();
        TypeVariable[] tps = ((Class) pt.getRawType())
            .getTypeParameters();
        for (int i = 0; i < tps.length; i++)
        {
          Class<?> value;
          if (ata[i] instanceof TypeVariable)
          {
            value = map.get(ata[i]);
          }
          else
          {
            value = (Class) ata[i];
          }
          map.put(tps[i], value);
        }
        if (pt.getRawType() == A.class)
        {
          break;
        }
        if (ata.length >= 1)
        {
          sc = ((Class) pt.getRawType()).getGenericSuperclass();
        }
      }
      else
      {
        sc = ((Class) sc).getGenericSuperclass();
      }
    }

    TypeVariable<?> myVar = A.class.getTypeParameters()[1];
    clazz = map.containsKey(myVar) ? (Class<T2>) map.get(myVar) : null;
  }
}

class Bar<T> extends A<T, String> {}
class Baz extends Bar<Integer> {}

class A2<T3, T1, T2> extends A<T1, T2> { }
class B<T> extends A2<Long, String, T> { }
class C extends B<Integer> { }
class D extends C { }

class Plain<T1, T2> extends A<T1, T2> {}

public class Test
{
  public static void main(String[] args)
  {
    new D();
    new Baz();
    new A<String, Integer>();
    new Plain<String, Integer>();
  }
}
like image 94
Nick Avatar answered Nov 10 '22 23:11

Nick