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:
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) 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:
reflectiveCall
needs to pass the type parameter which is unnecessary and tedious. At least the correct call is checked at compile time. canBeParameterOf
(such as typed parameters). 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:
getGenericParameterTypes()
could be empty, but let's assume all methods have an argument and this is checked beforehand. null
is called. Assume there aren't. catch
conditions could be more specific. 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#桌面应用程序的集成
下一篇: 反射检查对象是否是方法的有效通用参数