如何使用反射调用带有基本数据类型参数的方法?

3

我正在尝试创建一个方法invokeMethod(String methodName, Object...args),该方法可以在当前实例上调用父类的方法。我尝试了以下实现。

    public void invokeMethod(String methodName, Object...args) {
        //Array to hold the classes of the arguments
        Class<?>[] classes = new Class<?>[args.length]; 
        //Initialize each class in the array to the class of each argument 
        for(int i = 0; i < args.length; i++)
            classes[i] = args[i].getClass();
        try {
            //find the method
            Method m = this.getClass().getMethod(methodName, classes);
            //invoke the method
            m.invoke(this, args);
        } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
            e.printStackTrace();
        }
    }

这种实现的问题是,如果我尝试调用一个具有原始参数的方法,我会收到一个 NoSuchMethodException 异常,因为它正在寻找具有相应包装类类型的参数的方法。
例如,如果我尝试通过 invokeMethod("line", 50f, 50f, 50f, 50f) 调用一个签名为 line(float, float, float, float) 的方法,我会得到一个类似于异常的结果。
java.lang.NoSuchMethodException: DynamicSketch.line(java.lang.Float, java.lang.Float, java.lang.Float, java.lang.Float)
at java.base/java.lang.Class.getMethod(Class.java:2109)
at DynamicSketch.invokeMethod(DynamicSketch.java:32)
at DynamicSketch.setup(DynamicSketch.java:19)
at processing.core.PApplet.handleDraw(PApplet.java:2412)
at processing.awt.PSurfaceAWT$12.callDraw(PSurfaceAWT.java:1557)
at processing.core.PSurfaceNone$AnimationThread.run(PSurfaceNone.java:316)

有没有办法让我使用invokeMethod方法处理基本类型参数?
编辑:解决方案在这里不能用,因为我不知道在执行我的方法时签名中有哪些基本类型。我想要能够执行像size(int, int)line(float, float ,float ,float)这样的方法,而链接中的解决方案不易于实现此功能。我唯一看到的解决方案是定义一个if语句来处理超类中可能出现的每个方法,然后使用链接中的解决方案,但我希望有一种更简单的方法。

1
可能是[Java如何使用反射调用具有原始类型参数的方法]的重复问题(https://dev59.com/6mYr5IYBdhLWcg3wxM0k)。 - Andrey Tyukin
在链接的重复解决方案中,意味着您必须更改方法的签名,正是因为它无法区分基本类型和它们的包装器。 - Andrey Tyukin
@AndreyTyukin 我已经编辑了我的问题来解决这个问题。 - Lindstorm
我有一种模糊的感觉,你可能想看看apache commons中的getMatchingMethod - Andrey Tyukin
@AndreyTyukin 看起来 getMatchingMethod 的主要好处是我不需要 this.getClass() 部分,而且这个库有更多调用方法的选项。我理解得对吗? - Lindstorm
我不记得所有的细节了,我只是想提一下,有一个专门用于通过反射调用方法的Apache Commons库。那里有十几个invokeXyzMethod方法,也许其中一个对你有所帮助。 - Andrey Tyukin
1个回答

5
当参数,例如1010.0f传入方法时,它们会自动包装,因为参数类型是Object...
因此,您需要检查这些包装类型并将它们解包。您的for循环可以如下所示:
for(int i = 0; i < args.length; i++) {
    if (args[i].getClass() == Integer.class) {
        classes[i] = int.class;
    } else if (args[i].getClass() == Float.class) {
        classes[i] = float.class;
    } else if (args[i].getClass() == Double.class) {
        classes[i] = double.class;
    } else {
        classes[i] = args[i].getClass();
    }
}

我这里只添加了3个案例,你可以自己添加其他5个。
这意味着现在不能使用包装类型参数来调用方法。如果你也想调用它们,需要:
1. 解包所有的包装类型 2. 尝试找到一个使用非包装类型的方法 3. 如果没有找到,再次封装它们 4. 使用包装类型查找方法。
编辑:
正如Holger在评论中建议的那样,你也可以让JVM为你找到一个合适的方法,方法是使用
new Statement(this, methodName, args).execute();

关于 Statement 类的文档。


1
如果被调用的方法实际上期望的是 FloatInteger 而不是 floatint,那该怎么办呢? - Andrey Tyukin
@AndreyTyukin 这就是我回答的最后一部分所涉及的内容。如果是这种情况,OP必须检查是否存在具有原始参数的方法,如果不存在,则检查是否存在具有包装类型参数的方法。 - Sweeper
这仍然不能完全解决问题,如果有多个方法具有“本质上相同的签名”,但只在参数的“包装方式”上有所不同呢?invokeMethod方法的签名太过限制,无法消除所有的歧义情况。 - Andrey Tyukin
这种情况可能比人们想象的更常见。例如,在ArrayList<Integer>上使用put(int idx, Integer value)方法就是一个例子。它能正确处理吗?我不知道,这就是为什么我建议看一下apache commons reflection utils的原因。 - Andrey Tyukin
1
@Lindstorm,你可以在invokeMethod中添加一个名为preferWrapper的布尔参数。如果为真,则首先查找具有包装器参数的方法。如果不是,则首先查找具有原始参数的方法。 - Sweeper
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接