反思性检查对象是否是方法的有效泛型参数



如何使用反射检查给定对象是否将是方法的有效参数(其中参数和对象是泛型类型)?

为了得到一点背景,这是我想要达到的:

在使用反射方法调用时,我认为调用具有特定类型参数的所有方法会很好。这对于原始类型非常有效,因为您可以在它们的类对象上调用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中实现(例如类型化参数)。
  • 最大的问题是:类型参数本身不能是泛型的,所以ListListIntegers不能用作参数。

的问题我能做什么不同的事情来尽可能接近我的目标吗?我是否坚持使用匿名子类或传递类型参数?目前,我将满足于给出参数,因为这样可以保证编译时的安全性。

递归检查参数类型时,我需要注意什么吗?

是否有可能在解决方案2中允许泛型作为类型参数?

实际上,出于学习的目的,我想推出我自己的解决方案,而不是使用库,尽管我不介意看看一些库的内部工作原理。


为了让事情更清楚,我知道下面的例子,但尽量保持例子的干净:

  • 这可以在没有反射的情况下解决(同时重新设计一些需求并使用例如接口和内部类)。这是为了学习。我想要解决的问题实际上要大得多,但归结起来就是这样。
  • 我可以使用注释代替命名模式。我实际上是这样做的,但我想让示例尽可能地独立。
  • getGenericParameterTypes()返回的数组可以是空的,但是我们假设所有的方法都有一个参数,并且这是事先检查的。
  • 调用null时可能存在非静态方法失败。
  • catch条件可以更具体。

我是否坚持使用匿名子类或传递类型参数?

或多或少,但最好使用超类型标记模式创建子类,而不是创建值类型的子类。毕竟,您的值类型可能不允许子类。使用类型标记模式允许您接受泛型类型,而接受Class作为类型参数只允许原始类型(这就是为什么您必须接受List的组件类型,而不是类型本身)。

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

Guava对创建和使用TypeTokens有很好的支持。到目前为止,最简单的创建方法是创建一个匿名子类(注意:如果要重用它,请将其设置为常量):

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);
}

相关内容

  • 没有找到相关文章

最新更新