Search code examples
javareflectionguavatype-erasure

How do I get the argument of the formal method parameter type?


I have methods with a single parameter of type Event<Something> and need to extract the Something or its raw type. It may look like

 void method1(Event<MyEnum1> event) {}
 <E extends Enum<E>> method2(Event<E> event) {}
 <K, V> method3(Event<Map<K, V>> event) {}

and I'd like a function mapping

method1 -> MyEnum.class
method2 -> Enum.class
method3 -> Map.class

For method1, it's easy using (ParameterizedType) method.getGenericParameterTypes(). For method2, I can use Guava

TypeToken.of(method.getDeclaringClass())
    .method(method)
    .getTypeParameters()[0]
    .getBounds()

to obtain a Type and

TypeToken.of(method.getDeclaringClass())
    .method(method)
    .getParameters()
    .get(0)

to obtain the Parameter. I guess, I need to resolve the Parameter using the Type, but I'm stuck here.

I don't care much about method3, though it'd be good to know a general way. I don't care about the type of actual parameters (I'm aware of the erasure).


Solution

  • You don't need TypeToken for this. java.lang.reflect provides everything you need.

    You'll need to consider multiple cases. I'm going to assume the first parameter of any method provided is always of type Event (or really a parameterized type).

    You'll need to handle the 5 subtypes of Type. From your examples, you'll need to check whether the type argument is a concrete type (MyEnum), a type variable (E bounded by Enum), or a parameterized type (Map<K,V>).

    Additionally, there is WildcardType which you can handle like the type variable and extract bounds. Finally, you have to deal with generic array types with GenericArrayType (Event<T[]> or Event<List<String>[]>). I've left these out but it's just reapplying the same rules for the other types.

    I assume, for the type variable case, the bound might be some other type variable, so we need to recur until we find the concrete bound.

    // get raw type argument of first parameter
    public static Class<?> getRawTypeArgument(Method method) {
        Parameter parameter = method.getParameters()[0];
        Type type = parameter.getParameterizedType();
        /// assume it's parameterized
        ParameterizedType parameterizedType = (ParameterizedType) type;
        // assume there's one type argument
        Type typeArgument = parameterizedType.getActualTypeArguments()[0];
        if (typeArgument instanceof TypeVariable<?>) {
            TypeVariable<?> typeVariableArgument = (TypeVariable<?>) typeArgument;
            return recursivelyExtractBound(typeVariableArgument);
        } else if (typeArgument instanceof Class<?>) {
            return (Class<?>) typeArgument;
        } else if (typeArgument instanceof ParameterizedType) {
            ParameterizedType parameterizedTypeArgument = (ParameterizedType) typeArgument;
            return (Class<?>) parameterizedTypeArgument.getRawType();
        } 
        throw new AssertionError("Consider wildcard and generic type");
    }
    
    private static Class<?> recursivelyExtractBound(TypeVariable<?> typeVariable) {
        // assume first
        Type bound = typeVariable.getBounds()[0];
        if (bound instanceof Class<?>) {
            return (Class<?>) bound;
        } else if (bound instanceof TypeVariable<?>) {
            TypeVariable<?> nested = (TypeVariable<?>) bound;
            return recursivelyExtractBound(nested);
        } else if (bound instanceof ParameterizedType) {
            ParameterizedType parameterizedTypeArgument = (ParameterizedType) bound;
            return (Class<?>) parameterizedTypeArgument.getRawType();
        }
        throw new AssertionError("Are there others?");
    }
    

    With a small driver program

    public static void main(String[] args) throws Exception {
        Class<?> clazz = Example.class;
        for (Method method : clazz.getDeclaredMethods()) {
            if (!method.getName().startsWith("method")) {
                continue;
            }
            System.out.println("Method '" + method + "' -> " + getRawTypeArgument(method));
        }
    }
    

    the above prints out

    Method 'java.lang.Object com.example.Example.method3(com.example.root.Event)' -> interface java.util.Map
    Method 'java.lang.Object com.example.Example.method2(com.example.root.Event)' -> class java.lang.Enum
    Method 'void com.example.Example.method1(com.example.Event)' -> class com.example.MyEnum1