Java getMethod with subclass parameter
I'm writing a library that uses reflection to find and call methods dynamically. Given just an object, a method name, and a parameter list, I need to call the given method as though the method call were explicitly written in the code.
I've been using the following approach, which works in most cases:
static void callMethod(Object receiver, String methodName, Object[] params) {
Class<?>[] paramTypes = new Class<?>[params.length];
for (int i = 0; i < param.length; i++) {
paramTypes[i] = params[i].getClass();
}
receiver.getClass().getMethod(methodName, paramTypes).invoke(receiver, params);
}
However, when one of the parameters is a subclass of one of the supported types for the method, the reflection API throws a NoSuchMethodException
. For example, if the receiver's class has testMethod(Foo)
defined, the following fails:
receiver.getClass().getMethod("testMethod", FooSubclass.class).invoke(receiver, new FooSubclass());
even though this works:
receiver.testMethod(new FooSubclass());
How do I resolve this? If the method call is hard-coded there's no issue - the compiler just uses the overloading algorithm to pick the best applicable method to use. It doesn't work with reflection, though, which is what I need.
Thanks in advance!
Solution 1:
It's a bit longer than what you started with, but this does what you asked for... and a little more besides - for example, callMethod(receiver, "voidMethod") where voidMethod takes no arguments also works.
static void callMethod(Object receiver,
String methodName, Object... params) {
if (receiver == null || methodName == null) {
return;
}
Class<?> cls = receiver.getClass();
Method[] methods = cls.getMethods();
Method toInvoke = null;
methodLoop: for (Method method : methods) {
if (!methodName.equals(method.getName())) {
continue;
}
Class<?>[] paramTypes = method.getParameterTypes();
if (params == null && paramTypes == null) {
toInvoke = method;
break;
} else if (params == null || paramTypes == null
|| paramTypes.length != params.length) {
continue;
}
for (int i = 0; i < params.length; ++i) {
if (!paramTypes[i].isAssignableFrom(params[i].getClass())) {
continue methodLoop;
}
}
toInvoke = method;
}
if (toInvoke != null) {
try {
toInvoke.invoke(receiver, params);
} catch (Exception t) {
t.printStackTrace();
}
}
}
Solution 2:
receiver.testMethod(new FooSubclass());
even though this works:
If your testMethod
function has parameter of FooSuperClass
type:
public void testMethod(FooSuperClass object){}
then, while you are trying to get a matching method with reflection: getClass().getMethod("testMethod", FooSubclass.class)
will result in NoSuchMethodException
. Because this getMethod(String name, Class<?>... parameterTypes
function returns a Method
object which is a public member method with the given name
where parameterTypes
parameter is an array of Class objects that identify the method's formal parameter types. There is actually no such method is declared with signature testMedthod(FooSubClass object)
as the formal parameter type of the function is FooSuperClass
. So, the correct invocation is:
receiver.getClass().getMethod("testMethod", FooSuperClass.class)
.invoke(receiver, new FooSubclass());
or, passing the super class by calling SubClass.class.getSuperClass()
as follows:
receiver.getClass().getMethod("testMethod", FooSubClass.class.getSuperclass())
.invoke(receiver, new FooSubclass());
or, changing the method signature to: public void testMethod(FooSubClass object){}
and then invoke as you are doing now:
receiver.getClass().getMethod("testMethod", FooSubclass.class)
.invoke(receiver, new FooSubclass());