Get type of a generic parameter in Java with reflection

后端 未结 18 1973
死守一世寂寞
死守一世寂寞 2020-11-22 05:56

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

An example:

public final class Voodoo {
    public static void chill(List aListWithTy         


        
相关标签:
18条回答
  • 2020-11-22 06:23

    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.

    0 讨论(0)
  • 2020-11-22 06:26

    There is a solution actually, by applying the "anonymous class" trick and the ideas from the Super Type Tokens:

    public final class Voodoo {
        public static void chill(final List<?> aListWithSomeType) {
            // Here I'd like to get the Class-Object 'SpiderMan'
            System.out.println(aListWithSomeType.getClass().getGenericSuperclass());
            System.out.println(((ParameterizedType) aListWithSomeType
                .getClass()
                .getGenericSuperclass()).getActualTypeArguments()[0]);
        }
        public static void main(String... args) {
            chill(new ArrayList<SpiderMan>() {});
        }
    }
    class SpiderMan {
    }
    

    The trick lies in the creation of an anonymous class, new ArrayList<SpiderMan>() {}, in the place of the original (simple) new ArrayList<SpiderMan>(). The use of an anoymous class (if possible) ensures that the compiler retains information about the type argument SpiderMan given to the type parameter List<?>. Voilà !

    0 讨论(0)
  • 2020-11-22 06:29

    I want to try to break down the answer from @DerMike to explain:

    First, type erasure does not mean that the JDK eliminates type information at runtime. It's a method for allowing compile-time type checking and runtime type compatibility to coexist in the same language. As this block of code implies, the JDK retains the erased type information--it's just not associated with checked casts and stuff.

    Second, this provides generic type information to a generic class exactly one level up the heirarchy from the concrete type being checked--i.e. an abstract parent class with generic type parameters can find the concrete types corresponding to its type parameters for a concrete implementation of itself that directly inherits from it. If this class were non-abstract and instantiated, or the concrete implementation were two levels down, this wouldn't work (although a little bit of jimmying could make it apply to any predetermined number of levels beyond one, or up to the lowest class with X generic type parameters, et cetera).

    Anyway, on to the explanation. Here's the code again, separated into lines for ease of reference:

    1# Class genericParameter0OfThisClass = 
    2#     (Class)
    3#         ((ParameterizedType)
    4#             getClass()
    5#                .getGenericSuperclass())
    6#                    .getActualTypeArguments()[0];
    

    Let 'us' be the abstract class with generic types that contains this code. Reading this roughly inside out:

    • Line 4 gets the current concrete class' Class instance. This identifies our immediate descendant's concrete type.
    • Line 5 gets that class' supertype as a Type; this is us. Since we're a parametric type we can safely cast ourselves to ParameterizedType (line 3). The key is that when Java determines this Type object, it uses type information present in the child to associate type information with our type parameters in the new ParameterizedType instance. So now we can access concrete types for our generics.
    • Line 6 gets the array of types mapped into our generics, in order as declared in the class code. For this example we pull out the first parameter. This comes back as a Type.
    • Line 2 casts the final Type returned to a Class. This is safe because we know what types our generic type parameters are able to take and can confirm that they will all be classes (I'm not sure how in Java one would go about getting a generic parameter that doesn't have a Class instance associated with it, actually).

    ...and that's pretty much it. So we push type info from our own concrete implementation back into ourselves, and use it to access a class handle. we could double up getGenericSuperclass() and go two levels, or eliminate getGenericSuperclass() and get values for ourselves as a concrete type (caveat: I haven't tested these scenarios, they haven't come up for me yet).

    It gets tricky if your concrete children are be an arbitrary number of hops away, or if you're concrete and not final, and especially tricky if you expect any of your (variably deep) children to have their own generics. But you can usually design around those considerations, so this gets you most of the way.

    Hope this helped someone! I recognize this post is ancient. I'll probably snip this explanation and keep it for other questions.

    0 讨论(0)
  • 2020-11-22 06:31

    This is impossible because generics in Java are only considered at compile time. Thus, the Java generics are just some kind of pre-processor. However you can get the actual class of the members of the list.

    0 讨论(0)
  • 2020-11-22 06:32

    Because of type erasure the only way to know the type of the list would be to pass in the type as a parameter to the method:

    public class Main {
    
        public static void main(String[] args) {
            doStuff(new LinkedList<String>(), String.class);
    
        }
    
        public static <E> void doStuff(List<E> list, Class<E> clazz) {
    
        }
    
    }
    
    0 讨论(0)
  • 2020-11-22 06:33

    Appendix to @DerMike's answer for getting the generic parameter of a parameterized interface (using #getGenericInterfaces() method inside a Java-8 default method to avoid duplication):

    import java.lang.reflect.ParameterizedType; 
    
    public class ParametrizedStuff {
    
    @SuppressWarnings("unchecked")
    interface Awesomable<T> {
        default Class<T> parameterizedType() {
            return (Class<T>) ((ParameterizedType)
            this.getClass().getGenericInterfaces()[0])
                .getActualTypeArguments()[0];
        }
    }
    
    static class Beer {};
    static class EstrellaGalicia implements Awesomable<Beer> {};
    
    public static void main(String[] args) {
        System.out.println("Type is: " + new EstrellaGalicia().parameterizedType());
        // --> Type is: ParameterizedStuff$Beer
    }
    
    0 讨论(0)
提交回复
热议问题