简体   繁体   中英

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.

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

I created a method that will return you an array of types that parameterized the generic where 'null' indicates parameters that still exist. I tested it against your Foo hierarchy, as well as the example provided in the other answer that uses class 'A'.

Edit: Before I hadn't considered how reordering the types would affect the binding. In the new method, pass in the superclass you want the parameters of and the base class. As I iterate towards the superclass, I collect type parameters that are classes and map forward from the previous base class by comparing the parameter names. I hope that is clear enough.

public <S, B extends S> Class[] findTypeParameters(Class<B> base, Class<S> superClass) {
    Class[] actuals = new Class[0];
    for (Class clazz = base; !clazz.equals(superClass); clazz = clazz.getSuperclass()) {
        if (!(clazz.getGenericSuperclass() instanceof ParameterizedType))
            continue;

        Type[] types = ((ParameterizedType) clazz.getGenericSuperclass()).getActualTypeArguments();
        Class[] nextActuals = new Class[types.length];
        for (int i = 0; i < types.length; i++)
            if (types[i] instanceof Class)
                nextActuals[i] = (Class) types[i];
            else
                nextActuals[i] = map(clazz.getTypeParameters(), types[i], actuals);
        actuals = nextActuals;
    }
    return actuals;
}

private Class map(Object[] variables, Object variable, Class[] actuals) {
    for (int i = 0; i < variables.length && i < actuals.length; i++)
        if (variables[i].equals(variable))
            return actuals[i];
    return null;
}

@Test
public void findsTypeOfSuperclass() throws Exception {
    assertThat(findTypeParameters(A4.class, A1.class), arrayContaining(Integer.class, Boolean.class, String.class));
    assertThat(findTypeParameters(B4.class, A1.class), arrayContaining(Integer.class, Boolean.class, String.class));
    assertThat(findTypeParameters(C2.class, A1.class), arrayContaining(Integer.class, null, null));
    assertThat(findTypeParameters(D4.class, A1.class), arrayContaining(Integer.class, null, String.class));
}

class A1<T1, T2, T3> {}
class A2<T3, T2> extends A1<Integer, T2, T3> {}
class A3<T2> extends A2<String, T2> {}
class A4 extends A3<Boolean> {}

class B2<X, T3, Y, T2, Z> extends A1<Integer, T2, T3> {}
class B3<X, T2, Y, Z> extends B2<X, String, Y, T2, Z> {}
class B4<X> extends B3<X, Boolean, X, X> {}

class C2<T2, T3> extends A1<Integer, T2, T3> {}

class D2<T2> extends A1<Integer, T2, String> {}
class D3 extends D2 {}
class D4 extends D3 {}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM