【问题标题】:Reflectively checking whether a object is a valid generic argument to a method反射性地检查对象是否是方法的有效泛型参数
【发布时间】: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&lt;List&lt;Integer&gt;&gt;),这都应该是可能的。

显然这是行不通的,因为 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&lt;Integer&gt;(){} 中的{},它创建了一个匿名内部类)。这显然不太理想:创建不必要的类对象并且容易出错。这是我能想到绕过类型擦除的唯一方法。

尝试 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 的IntegersList 用作参数。

问题

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

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

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

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


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

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

【问题讨论】:

标签: java generics reflection type-erasure


【解决方案1】:

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

或多或少,但最好使用super type token 模式进行子类化,而不是对值类型进行子类化。毕竟,您的值类型可能不允许子类。使用类型标记模式允许您接受泛型类型,而接受 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);
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-08-10
    • 1970-01-01
    • 1970-01-01
    • 2016-01-14
    • 2022-01-04
    • 2011-01-24
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多