Reflectively checking whether a object is a valid generic argument to a method

How do you check using reflection whether a given object would be a valid parameter of a method (where the parameter and the object are generic types)?

To get a bit of background, this is what I'm trying to achieve:

While playing with reflective method calls I thought that it would be nice to call all methods that have a parameter of a specific type. This works well for raw types as you can call isAssignableFrom(Class<?> c) on their class objects. However, when you start throwing in generics into the mix it suddenly isn't that easy because generics weren't part of reflection's original design and because of type erasure.

The problem is larger but it basically boils down to the following:

Ideal solution

Ideally the code

import java.lang.reflect.*;
import java.util.*;


public class ReflectionAbuse {

    public static void callMeMaybe(List<Integer> number) {
        System.out.println("You called me!");
    }

    public static void callMeAgain(List<? extends Number> number) {
        System.out.println("You called me again!");
    }

    public static void callMeNot(List<Double> number) {
        System.out.println("What's wrong with you?");
    }

    public static <T> void reflectiveCall(List<T> number){
        for(Method method : ReflectionAbuse.class.getDeclaredMethods()) {
            if(method.getName().startsWith("call")) {
                if(canBeParameterOf(method, number)) {
                    try {
                        method.invoke(null, number);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    public static <T> boolean canBeParameterOf(Method method, List<T> number) {
        // FIXME some checks missing
        return true;
    }

    public static void main(String[] args) {
        reflectiveCall(new ArrayList<Integer>());
    }

}

would print

You called me!
You called me again!

This should be possible regardless of how T look like (ie it could be another generic Type, such as List<List<Integer>> ).

Obviously this can't work because the type T is erased and unknown at runtime.

Attempt 1

The first thing I could get working is something like this:

import java.lang.reflect.*;
import java.util.*;


public class ReflectionAbuse {

            public static void callMeMaybe(ArrayList<Integer> number) {
                System.out.println("You called me!");
            }

            public static void callMeAgain(ArrayList<? extends Number> number) {
                System.out.println("You called me again!");
            }

            public static void callMeNot(ArrayList<Double> number) {
                System.out.println("What's wrong with you?");
            }

    public static <T> void reflectiveCall(List<T> number){
        for(Method method : ReflectionAbuse.class.getDeclaredMethods()) {
            if(method.getName().startsWith("call")) {
                if(canBeParameterOf(method, number)) {
                    try {
                        method.invoke(null, number);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    public static <T> boolean canBeParameterOf(Method method, List<T> number) {
        return method.getGenericParameterTypes()[0].equals(number.getClass().getGenericSuperclass());
    }

    public static void main(String[] args) {
        reflectiveCall(new ArrayList<Integer>(){});
    }

}

which only prints

You called me!

However, this has some additional caveats:

  • It only works for direct instances and inheritance hierarchy is not taken into account as the Type interface does not provide the needed methods. A cast here and there could definitely help to find this out (see also my second attempt)
  • The argument of reflectiveCall actually needs to be a subclass of the wanted parameter type (notice the {} in new ArrayList<Integer>(){} which create an anonymous inner class). That's obviously less than ideal: Creates unnecessary class objects and is error prone. This is the only way I could think of getting around type erasure.
  • Attempt 2

    Thinking about the missing type in the ideal solution due to erasure, one could also pass the type as an argument which gets quite close to the ideal:

    import java.lang.reflect.*;
    import java.util.*;
    
    
    public class ReflectionAbuse {
    
        public static void callMeMaybe(List<Integer> number) {
            System.out.println("You called me!");
        }
    
        public static void callMeAgain(List<? extends Number> number) {
            System.out.println("You called me again!");
        }
    
        public static void callMeNot(List<Double> number) {
            System.out.println("What's wrong with you?");
        }
    
        public static <T> void reflectiveCall(List<T> number, Class<T> clazz){
            for(Method method : ReflectionAbuse.class.getDeclaredMethods()) {
                if(method.getName().startsWith("call")) {
                    Type n = number.getClass().getGenericSuperclass();
                    if(canBeParameterOf(method, clazz)) {
                        try {
                            method.invoke(null, number);
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    
        public static <T> boolean canBeParameterOf(Method method, Class<T> clazz) {
            Type type = ((ParameterizedType)method.getGenericParameterTypes()[0]).getActualTypeArguments()[0];
            if (type instanceof WildcardType) {
                return ((Class<?>)(((WildcardType) type).getUpperBounds()[0])).isAssignableFrom(clazz);
            }
            return ((Class<?>)type).isAssignableFrom(clazz);
        }
    
        public static void main(String[] args) {
            reflectiveCall(new ArrayList<Integer>(), Integer.class);
        }
    
    }
    

    which actually prints the correct solution. But this is also not without negative sides:

  • The user of reflectiveCall needs to pass the type parameter which is unnecessary and tedious. At least the correct call is checked at compile time.
  • Inheritance between the type parameters is not completely taken into account and there are definitely many cases left which need to be implemented in canBeParameterOf (such as typed parameters).
  • And the biggest problem: The type parameter can't be generic in itself, so a List of List s of Integers can't be used as an argument.
  • The question

    Is there anything I could do differently to get as close as possible to my goal? Am I stuck with either using anonymous subclasses or passing the type parameter? For the moment, I'll settle with giving the parameter as this gives you compile time safety.

    Is there anything I need to be aware of when recursively checking the parameter types?

    Is there any possibility to allow generics a type parameter in solution 2?

    Actually, for learning purposes I would like to roll my own solution instead of using a library although I wouldn't mind taking a look at the inner workings of some.


    Just to make things clear, I'm for example aware of the following but try to keep the examples clean:

  • This could potentially be solved without reflection (while redesigning some of the requirements and using for example interfaces and inner classes). This is for learning purposes. The problem I'd like to solve is actually quite a bit larger but this is what it boils down to.
  • Instead of using a naming pattern I could use annotations. I actually do that, but I'd like to keep the examples as self-contained as possible.
  • The array returned by getGenericParameterTypes() could be empty, but let's assume all methods have an argument and this is checked beforehand.
  • There could be non-static methods which fail when null is called. Assume there aren't.
  • The catch conditions could be more specific.
  • Some casts need to be more safe.

  • Am I stuck with either using anonymous subclasses or passing the type parameter?

    More or less, but better to subclass using the super type token pattern instead of subclassing your value type. After all, your value type might not allow subclasses. Using the type token pattern allows you to accept generic types, whereas accepting the Class as a type parameter only allows raw types (which is why you had to take the component type of List , not the type itself).

    public static <T> void reflectiveCall(TypeToken<T> type, T value)
    

    Guava has great support for creating and using TypeToken s. By far the simplest way to create one is by creating an anonymous subclass (note: if it's going to be reused, make it a constant):

    reflectiveCall(new TypeToken<List<Integer>>() {}, new ArrayList<Integer>());
    

    Once you have this, canBeParameterOf becomes much easier to implement.

    public static boolean canBeParameterOf(Method method, TypeToken<?> givenType) {
        Type[] argTypes = method.getGenericParameterTypes();
    
        return argTypes.length != 0 && 
            TypeToken.of(argTypes[0]).isAssignableFrom(givenType);
    }
    
    链接地址: http://www.djcxy.com/p/13348.html

    上一篇: Facebook与C#桌面应用程序的集成

    下一篇: 反射检查对象是否是方法的有效通用参数