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