Reflective check to see if an object is a valid general method argument

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) { // FIXME some checks missing return true; } public static void main(String[] args) { reflectiveCall(new ArrayList<Integer>()); } } 

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.
+7
source share
1 answer

Am I stuck with using anonymous subclasses or pass in a type parameter?

More or less, but better subclasses using the supertext type of the token instead of subclassing your value type. In the end, your value type may not allow subclasses. Using a type token template allows you to accept generic types, while accepting the Class parameter as a type only allows raw types (so you had to take the type of the List component, not the type itself).

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

Guava has great support for creating and using TypeToken . The easiest way to create one of them is to create an anonymous subclass (note: if it will be reused, make it a constant):

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

Once you do, canBeParameterOf will be 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); } 
+3
source

All Articles