简体   繁体   中英

Get a generic class field type with Java Reflections

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?

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

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

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