7

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.

2 Answers 2

3

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>();
  }
}
Sign up to request clarification or add additional context in comments.

Comments

3

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 {}

1 Comment

Thanks for the reply :). It does work for me. A thing I realized is that you didn't take into account the case when you change the order of the parameters, for example Bar<U, T> extends Foo<T, U>. As you are putting the argument class in the first empty position in the array, in this example, you will get the wrong types. Won't you?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.