Java反射:根据参数和签名自动调用正确的重载方法

6
说有一个名为doTask的重载方法:
public class Game {
  void doTask(Joker joker);
  void doTask(Batman batman, Robin robin);
}

如果已知方法的名称("doTask")以及一个元素数量和类型未知的参数数组,我想调用正确的方法。

通常需要至少三个步骤:
1. 找到参数的数量和类型,并创建一个数组 Class[] myTypes.
2. 确定正确的重载方法,即 Method rightMethod = game.getClass().getMethod("doTask", myTypes);
3. 调用该方法: rightMethod.invoke(paramArray).

是否存在一种方法,可以让Java反射自动识别要使用的正确重载方法,并避免执行步骤1和2? 我认为理想情况下,这将像这样:
Library.invoke("doTask", paramArray);


这对我来说听起来有些不对。你在尝试什么?为什么你不知道参数的数量?你从哪里得到它们?它们是什么形式?当你不知道参数的数量时,你怎么能确定有一个带有正确数量参数的实现?将参数列表直接传递给doTask()并让实现决定如何处理它们是否可行? - lupz
重载:带有一些最小的设计气味。反射:更大的气味。长话短说:你打算解决的潜在问题是什么? - GhostCat
Lupz和GhostCat,我同意你们的观点,如果我从头开始编写整个项目,它不会被写成这样。但现在doTask()方法已经被编写(并被其他代码使用)。我正在编写一个包装函数myFunc(Object... paramArray),加入额外的业务逻辑,以调用正确的doTask() - flow2k
另一种选择当然是为每个重载的 doTask() 方法编写单独的包装器。但这将复制大量业务逻辑代码,并使其难以维护。 - flow2k
这个“业务逻辑”对于所有参数都是相同的吗?如果不是,那么您已经可以使用一个包装器方法识别应该调用哪个“doTask()”。 - Yazan
2个回答

6

如果需要返回值,可以使用以下两种java.beans包中的工具:java.beans.StatementExpression

Game game = new Game();
Joker joker = new Joker();
Statement st = new Statement(game, "doTask", new Object[]{ joker });
st.execute();

然而,它只适用于public方法。

此外,与java.lang.reflect.Method不同的是,该工具未被改进以支持可变参数,因此您必须手动创建参数数组。

可以证明,它根据参数类型选择正确的目标方法,这些类型不一定与参数类型相同:

ExecutorService es = Executors.newSingleThreadExecutor();
class Foo implements Callable<String> {
    public String call() throws Exception {
        return "success";
    }
}
// has to choose between submit(Callable) and submit(Runnable)
// given a Foo instance
Expression ex = new Expression(es, "submit", new Object[]{ new Foo() });
Future<?> f = (Future<?>)ex.getValue();
System.out.println(f.get());
es.shutdown();

感谢您提供这么详细的答案。一开始我对“参数类型不一定等于参数类型”感到困惑,但我认为它的意思是:new Foo()Foo 类型(参数类型),与 Callable<> 类型(参数类型)不完全相同。但由于 FooCallable 的子类,所以会调用正确的 submit。我理解得对吗,@Holger? - flow2k
另外,由于ExpressionStatement位于beans包中,它们只适用于JavaBeans类,对吗? - flow2k
@Holger 哦,该死...我不知道这会存在。 - Eugene
1
@flow2k:没错,仅仅调用 getMethod("submit", argument.getClass()); 就会失败,因为没有 submit(Foo) 方法,所以实现必须检查每个方法的适用性,正如 Gerald Mücke 所详细阐述的那样。值得庆幸的是,这已经被实现了。关于“Java Beans”,bean 模式非常广泛,几乎匹配每个类。唯一的限制已经在我的答案中提到了;它只适用于 public 方法。我的示例演示了这一点,它使用了 ExecutorService 和内部类 Foo,并且都没有采取任何显式操作来成为 Java Bean。 - Holger

2

首先,回答你的问题 - 不,没有这样的设施。

其次,第二步稍微复杂一些,因为仅从参数创建类数组并调用 getMethod 是不够的。

实际上,您需要遍历所有匹配名称和参数数量的方法,并比较方法的参数类型与给定参数类型的赋值兼容性(即 methodArgType.isAssignableFrom(paramType)),以确保正确反映方法参数类型的兼容子类型。使用可变参数会稍微复杂一些。


广为人知的是,有这样一个设施 - Holger
@ Gerald Mücke感谢您的建议。关于您对第二步和第三步的评论,似乎您可以简单地执行以下操作:Class [] argTypeArray = new Class [paramArray.length]; for (int n = 0; n <paramArray.length; n ++) {argTypeArray [n] = paramArray [n] .getClass();} - flow2k
Method rightMethod = Game.class.getMethod("doTask",argTypeArray) - flow2k
最后,在第三步中,rightMethod.invoke(game, paramArray) - flow2k

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