【发布时间】:2013-05-08 21:32:07
【问题描述】:
如何使用反射检查给定对象是否是方法的有效参数(其中参数和对象是泛型类型)?
为了了解一些背景知识,这就是我想要实现的目标:
在使用反射方法调用时,我认为调用所有具有特定类型参数的方法会很好。这适用于原始类型,因为您可以在其类对象上调用 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中实现(比如类型参数)。 - 还有最大的问题:类型参数本身不能是泛型的,所以不能将
Lists 的Integers的List用作参数。
问题
我可以做些什么不同的事情来尽可能接近我的目标吗?我是否坚持使用匿名子类或传递类型参数?目前,我将提供参数,因为这可以确保编译时安全。
递归检查参数类型时有什么需要注意的吗?
是否有可能在解决方案 2 中允许泛型作为类型参数?
实际上,出于学习目的,我想推出自己的解决方案,而不是使用库,尽管我不介意看看一些内部工作原理。
为了清楚起见,例如,我知道以下内容,但尽量保持示例简洁:
- 这可以在没有反射的情况下解决(同时重新设计一些需求并使用例如接口和内部类)。这是出于学习目的。我想解决的问题实际上有点大,但归根结底就是这样。
- 我可以使用注释而不是使用命名模式。我实际上是这样做的,但我想尽可能地保持示例独立。
getGenericParameterTypes()返回的数组可能为空,但我们假设所有方法都有一个参数,并且事先检查过。- 当调用
null时,可能有一些非静态方法会失败。假设没有。 catch条件可能更具体。- 有些演员需要更安全。
【问题讨论】:
标签: java generics reflection type-erasure