7

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

为了获得一些背景知识,这就是我想要实现的目标:

在使用反射方法调用时,我认为调用所有具有特定类型参数的方法会很好。这适用于原始类型,因为您可以调用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 Listof Lists ofIntegers不能作为实参。

问题

有什么我可以做的不同的事情来尽可能接近我的目标吗?我是否坚持使用匿名子类或传递类型参数?目前,我将通过提供参数来解决,因为这可以为您提供编译时安全性。

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

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

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


为了清楚起见,例如,我知道以下内容,但请尽量保持示例清洁:

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

1 回答 1

3

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

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

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

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

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);
}
于 2013-05-09T04:26:04.937 回答