How can you check if a given object reflects a valid method parameter (where the parameter and the object are common types)?
To get a little background, this is what I am trying to achieve:
During playback using reflexive methods, I thought it would be nice to call all methods that have a parameter of a certain type. This works well for raw types, since you can call isAssignableFrom(Class<?> c) in your class objects. However, when you start throwing generics into the mix, it is suddenly not that simple because generics were not part of the original reflection design and because of the erasure of styles.
The problem is bigger, but basically it boils down to the following:
The perfect solution
Ideally 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 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) {
will print
You called me! You called me again!
This should be possible no matter what T looks like (i.e., it may be another general type, such as List<List<Integer>> ).
Obviously, this is not possible because type T is erased and unknown at runtime.
Attempt 1
The first thing I could get was 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 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 prints only
You called me!
However, this has some additional caveats:
- It works only for direct instances, and the inheritance hierarchy is not taken into account, since the
Type interface does not provide the necessary methods. The cast here and there can definitely help find this (see also my second attempt). - The
reflectiveCall argument must indeed be a subclass of the required parameter type (note the {} in new ArrayList<Integer>(){} , which create an anonymous inner class). This is clearly less than ideal: it creates unnecessary class objects and is error prone. This is the only way I could think of getting around erasing styles.
Attempt 2
When thinking of a missing type in an ideal solution due to erasure, you can also pass the type as an argument that approaches 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 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 aspects:
- The
reflectiveCall user must pass a type parameter that is not necessary and tedious. At least the correct call is checked at compile time. - Inheritance between type parameters is not fully taken into account and, of course, there remain many cases that need to be implemented in
canBeParameterOf (for example, typed parameters). - And the biggest problem: a type parameter cannot be shared by itself, so the
List List of Integers cannot be used as an argument.
Question
Is there anything I could do differently to get closer to my goal? Am I stuck either using anonymous subclasses or passing in a type parameter? At the moment, I agree with the provision of the parameter, as this gives you the opportunity to save time.
Is there anything I need to know when recursively checking parameter types?
Is it possible to allow generics type parameter in solution 2?
Actually, for educational purposes, I would like to curtail my own solution instead of using the library, although I would not mind looking at the internal work of some.
Just to make everything clear, for example, I know the following, but I try to keep these examples clean:
- This can potentially be resolved without reflection (when processing some requirements and using, for example, interfaces and inner classes). This is for educational purposes. The problem that I would like to solve is actually a little more, but this is what it comes down to.
- Instead of using a naming pattern, I could use annotations. I really do this, but I would like the examples to be as autonomous as possible.
- The array returned by
getGenericParameterTypes() may be empty, but suppose all methods are assumed to have an argument, and this is checked beforehand. - There may be non-static methods that fail when
null called. Suppose no. catch conditions may be more specific.- Some roles should be more secure.