4

I have the following class structure:

public class GenClass<T> {
    public T elem;
}

I use it in the following way:

public class Test {
    public GenClass<UUID> data;

Now I want to get the type of elem using the Field object of data(Test.class.getField("data")) But when I use getType to retrieve the class the Generic information is stripped away.

How can I map the generic Information from getGenericType to the class object to retrieve the field with a correct type?

Edit: Since there are some misunderstandings, I try to clarify my problem. Consider this example:

public class AClass<T, Q> {
    public Q elem;
    // some other code using T...
}

public class BClass<T, Q> {
    public T elem;
    // some other code using Q...
}

Now I want a function to get the class of elem:

public class Test {
    public AClass<UUID, String> a;
    public BClass<Integer, Float> b;

    void do() throws Exception {
        Field aField = Test.class.getField("a");
        Field bField = Test.class.getField("b");

        getType(aField, "elem"); // should return String.class
        getType(bField, "elem"); // should return Integer.class
    }

    Class<?> getType(Field f, String classField) {
        // ???
    } 
}

How did I need to implement getType to get my desired result?

darkbit
  • 153
  • 1
  • 7
  • 1
    Check http://stackoverflow.com/questions/1901164/get-type-of-a-generic-parameter-in-java-with-reflection – user2953113 Sep 25 '15 at 14:04
  • But I don't know, which of the actual type arguments for the field is used for elem. Maybe I have a class like AClass and then declare `B data`. How do I know, that data is the third type argument? – darkbit Sep 25 '15 at 14:26

2 Answers2

5

You have the Type object corresponding to your field data, from calling getGenericType.

Type t = f.getGenericType();  // f is your Field

The Type interface and its implementations represent different cases of what kinds of types could be present here. Because data's type is GenClass<UUID>, parameterized with a type parameter, the Type returned here is actually a ParameterizedType.

ParameterizedType pt = (ParameterizedType) t;

Generally there could be multiple generic type parameters, but you have only one here. Call ParameterizedType's getActualTypeArguments method.

Type parameter = pt.getActualTypeArguments()[0];

Yes, we have another Type instance, but this one represents the generic type parameter of the Field, not the Field itself. Because you supplied a class type in the Test class, this Type is nothing other than an ordinary Class -- UUID.class.

System.out.println(parameter instanceof Class);
System.out.println(parameter == UUID.class);

Output:

true
true
rgettman
  • 176,041
  • 30
  • 275
  • 357
1

OK, so what you need to do is get both the type parameters on the raw type of your a/b field, and the actual type parameters on the ParameterizedType. The raw type Parameter can then be matched up with the elem field on the A/BClass you're interested in, but then you use that to resolve/match to the the actual type (the arrays line up).

public class TestGenerics {
    public AClass<UUID, String> a;
    public BClass<Integer, Float> b;

    void getFieldTypes() throws NoSuchFieldException, SecurityException {
        Field aField = TestGenerics.class.getField("a");
        Field bField = TestGenerics.class.getField("b");

        Class<?> aFieldElem = getType(aField, "elem"); // should return String.class
        Class<?> bFieldElem = getType(bField, "elem"); // should return Integer.class

        System.out.println("a field, elem field type: " + aFieldElem.getSimpleName());
        System.out.println("b field, elem field type: " + bFieldElem.getSimpleName());
    }

    Class<?> getType(final Field f, final String classField) throws NoSuchFieldException, SecurityException {
        Type genericType = f.getGenericType();
        //going to assume this. In reality you'd want to do an instanceof first
        ParameterizedType pt = (ParameterizedType) genericType;
        Class<?> rawType = (Class<?>) pt.getRawType();
        Type[] actualTypeParams = pt.getActualTypeArguments();
        TypeVariable<?>[] rawTypeParams = rawType.getTypeParameters();

        Field classFieldOnF = rawType.getField(classField);

        genericType = getResolvedType(classFieldOnF.getGenericType(), rawTypeParams, actualTypeParams);

        //same here, you'd need to do an instanceof first
        return (Class<?>) genericType;
    }

    private Type getResolvedType(final Type genericType, final Type[] rawTypeParams, final Type[] actualTypes) {
        for (int i = 0; i < rawTypeParams.length; i++) {
            if (genericType == rawTypeParams[i]) return actualTypes[i];
        }
        return genericType;
    }

    public static void main(final String[] args) throws NoSuchFieldException, SecurityException {
        TestGenerics test = new TestGenerics();
        test.getFieldTypes();
    }
}

Output is:

a field, elem field type: String
b field, elem field type: Integer
monty
  • 1,543
  • 14
  • 30