Get type of a generic parameter in Java with refle

2018-12-31 07:35发布

Is it possible to get the type of a generic parameter?

An example:

public final class Voodoo {
    public static void chill(List<?> aListWithTypeSpiderMan) {
        // Here I'd like to get the Class-Object 'SpiderMan'
        Class typeOfTheList = ???;
    }

    public static void main(String... args) {
        chill(new ArrayList<SpiderMan>());
    }
}

16条回答
骚的不知所云
2楼-- · 2018-12-31 08:10

Nope, that is not possible. Due to downwards compatibility issues, Java's generics are based on type erasure, i.a. at runtime, all you have is a non-generic List object. There is some information about type parameters at runtime, but it resides in class definitions (i.e. you can ask "what generic type does this field's definition use?"), not in object instances.

查看更多
呛了眼睛熬了心
3楼-- · 2018-12-31 08:13

You cannot get a generic parameter from a variable. But you can from a method or field declaration:

Method method = getClass().getDeclaredMethod("chill", List.class);
Type[] params = method.getGenericParameterTypes();
ParameterizedType firstParam = (ParameterizedType) params[0];
Type[] paramsOfFirstGeneric = firstParam.getActualTypeArguments();
查看更多
与君花间醉酒
4楼-- · 2018-12-31 08:16

No it isn't possible.

You can get a generic type of a field given a class is the only exception to that rule and even that's a bit of a hack.

See Knowing type of generic in Java for an example of that.

查看更多
墨雨无痕
5楼-- · 2018-12-31 08:19

You can get the type of a generic parameter with reflection like in this example that I found here:

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

public class Home<E> {
    @SuppressWarnings ("unchecked")
    public Class<E> getTypeParameterClass(){
        Type type = getClass().getGenericSuperclass();
        ParameterizedType paramType = (ParameterizedType) type;
        return (Class<E>) paramType.getActualTypeArguments()[0];
    }

    private static class StringHome extends Home<String>{}
    private static class StringBuilderHome extends Home<StringBuilder>{}
    private static class StringBufferHome extends Home<StringBuffer>{}   

    /**
     * This prints "String", "StringBuilder" and "StringBuffer"
     */
    public static void main(String[] args) throws InstantiationException, IllegalAccessException {
        Object object0 = new StringHome().getTypeParameterClass().newInstance();
        Object object1 = new StringBuilderHome().getTypeParameterClass().newInstance();
        Object object2 = new StringBufferHome().getTypeParameterClass().newInstance();
        System.out.println(object0.getClass().getSimpleName());
        System.out.println(object1.getClass().getSimpleName());
        System.out.println(object2.getClass().getSimpleName());
    }
}
查看更多
步步皆殇っ
6楼-- · 2018-12-31 08:21

I've coded this for methods which expect to accept or return Iterable<?...>. Here is the code:

/**
 * Assuming the given method returns or takes an Iterable<T>, this determines the type T.
 * T may or may not extend WindupVertexFrame.
 */
private static Class typeOfIterable(Method method, boolean setter)
{
    Type type;
    if (setter) {
        Type[] types = method.getGenericParameterTypes();
        // The first parameter to the method expected to be Iterable<...> .
        if (types.length == 0)
            throw new IllegalArgumentException("Given method has 0 params: " + method);
        type = types[0];
    }
    else {
        type = method.getGenericReturnType();
    }

    // Now get the parametrized type of the generic.
    if (!(type instanceof ParameterizedType))
        throw new IllegalArgumentException("Given method's 1st param type is not parametrized generic: " + method);
    ParameterizedType pType = (ParameterizedType) type;
    final Type[] actualArgs = pType.getActualTypeArguments();
    if (actualArgs.length == 0)
        throw new IllegalArgumentException("Given method's 1st param type is not parametrized generic: " + method);

    Type t = actualArgs[0];
    if (t instanceof Class)
        return (Class<?>) t;

    if (t instanceof TypeVariable){
        TypeVariable tv =  (TypeVariable) actualArgs[0];
        AnnotatedType[] annotatedBounds = tv.getAnnotatedBounds();///
        GenericDeclaration genericDeclaration = tv.getGenericDeclaration();///
        return (Class) tv.getAnnotatedBounds()[0].getType();
    }

    throw new IllegalArgumentException("Unknown kind of type: " + t.getTypeName());
}
查看更多
不流泪的眼
7楼-- · 2018-12-31 08:22

The quick answer the the Question is no you can't, because of Java generic type erasure.

The longer answer would be that if you have created your list like this:

new ArrayList<SpideMan>(){}

Then in this case the generic type is preserved in the generic superclass of the new anonymous class above.

Not that I recommend doing this with lists, but it is a listener implementation:

new Listener<Type>() { public void doSomething(Type t){...}}

And since extrapolating the generic types of super classes and super interfaces change between JVMs, the generic solution is not as straight forward as some answers might suggest.

Here is now I did it.

查看更多
登录 后发表回答