Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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!

like image 241
exists-forall Avatar asked Nov 10 '13 03:11

exists-forall


2 Answers

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();
    }
  }
}

like image 126
Elliott Frisch Avatar answered Oct 23 '22 13:10

Elliott Frisch


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());
like image 36
Sage Avatar answered Oct 23 '22 12:10

Sage