反射检查对象是否是方法的有效通用参数
如何使用反射来检查给定对象是否是方法的有效参数(参数和对象是泛型类型)?
为了获得一些背景知识,我正在努力实现这一目标:
在使用反射方法调用时,我认为可以调用具有特定类型参数的所有方法。 这对于原始类型非常适用,因为您可以在其类对象上调用isAssignableFrom(Class<?> c)
。 然而,当你开始在混合中投入泛型时,它突然间并不那么容易,因为泛型不是反射的原始设计的一部分,也因为类型删除。
问题较大,但基本归结为以下几点:
理想的解决方
理想的代码
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>());
}
}
会打印
You called me!
You called me again!
这应该是可能的,不管T
如何看起来像(即它可能是另一个泛型类型,例如List<List<Integer>>
)。
显然这不能工作,因为类型T
在运行时被擦除并且未知。
尝试1
我能做的第一件事就是这样的:
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>(){});
}
}
只打印
You called me!
但是,这有一些额外的警告:
Type
接口不提供所需的方法,所以不会考虑继承层次结构。 在这里和那里演员肯定可以帮助你找到答案(另见我的第二次尝试) reflectiveCall
实际上需要为想要的参数类型的子类(注意{}
在new ArrayList<Integer>(){}
其中创建匿名内部类)。 这显然不太理想:创建不必要的类对象,并且容易出错。 这是我可以考虑绕过类型擦除的唯一方法。 尝试2
考虑到由于删除而在理想解决方案中丢失的类型,人们也可以通过这种类型作为一个非常接近理想的理由:
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);
}
}
这实际上打印正确的解决方案。 但这也不是没有消极的一面:
reflectiveCall
的用户需要传递不必要和乏味的类型参数。 至少在编译时检查正确的调用。 canBeParameterOf
还有很多需要实现的canBeParameterOf
(例如类型化参数)。 Integers
List
List
不能用作参数。 这个问题
有什么我可以做的不同,以尽可能接近我的目标? 我坚持使用匿名子类或传递类型参数? 目前,我会解决给出的参数,因为这可以让你编译时间安全。
在递归检查参数类型时是否需要注意什么?
有没有可能让泛型成为解决方案2中的类型参数?
其实,为了学习的目的,我想推出我自己的解决方案,而不是使用库,尽管我不介意看看一些内部工作原理。
为了清楚起见,我例如知道以下内容,但试着保持例子的清洁:
getGenericParameterTypes()
返回的数组可能是空的,但我们假设所有方法都有一个参数,并且事先检查它。 null
时失败。 假设没有。 catch
条件可能更具体。 我坚持使用匿名子类或传递类型参数?
或多或少,但更好使用超类型标记模式而不是继承你的值类型。 毕竟,你的值类型可能不允许子类。 使用类型标记模式允许您接受泛型类型,而将Class
接受为类型参数只允许原始类型(这就是为什么您必须采用List
类型的组件,而不是类型本身)。
public static <T> void reflectiveCall(TypeToken<T> type, T value)
Guava对创建和使用TypeToken
有很大的支持。 到目前为止,创建一个最简单的方法是创建一个匿名的子类(注意:如果它将被重用,使其成为一个常量):
reflectiveCall(new TypeToken<List<Integer>>() {}, new ArrayList<Integer>());
一旦你有了这个, canBeParameterOf
变得更容易实现。
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/13347.html
上一篇: Reflectively checking whether a object is a valid generic argument to a method