Pass the resulting object to a method requiring a superclass using java reflection?

EDIT: I was not clear. I have to use reflection because I interpret from the command line. I am doing the equivalent of reflecting the code examples that I have provided.

I hope this is not a duplicate, because it seems that it needs to be done every day.

I have class A and class B that extends A. If I have a method in class C, like public void doSomething (A a), how can I use reflection to pass object B to this function? I want to make an equivalent (reflection):

B b = new B(); //B inherits from A C c = new C(); c.doSomething(b); // method signature is doSomething(A a); 

What I did (using reflection):

  • get objects that are function arguments.
  • get argument classes
  • find a method based on argument classes.
  • call a method by passing objects in the argument.

This works fine if I'm going to pass an object A to C.doSomething (...). However, if I try to pass object B to C.doSomething (...), it fails in step 3 with this error:

java.lang.NoSuchMethodException: C.doSomething (B)

How can C.doSomething be obtained to recognize that B is A? (when searching for a method using the getDeclaredMethod method (String name, Class ... parameterTypes) and passing B.class as the parameter type)

EDIT:

I will post my own decision if someone wants to see a quickly hacked way to do what Roland Illig suggested. In this example, I reference these pre-made variables:

 String methodToken; //the name of the method Object obj; //the object whose method we are trying to call Object[] args; //the user given arguments for the method Class[] argTypes; //the types of the args gotten by args[i].getClass(); 

So...

  //*** try to get the specified method from the object Method m = null; // if we are looking for a no-arg version of the method: if(null == args) { try { m = obj.getClass().getMethod(methodToken, argTypes); } catch ( /*errors*/ ) { // do stuff } } else // if we are looking for a version of the method that takes arguments { // we have to do this type of lookup because our user arguments could be // subclasses of the arguments required by the method. getMethod will not // find a match in that case. try { boolean matchFound = false; Class c = obj.getClass(); do { // for each level in the inheritance hierarchy: // get all the methods with the right name //(matching the name that the user supplied for the method) Method[] methodList = c.getMethods(); ArrayList<Method> matchingMethods = new ArrayList<Method>(); for( Method meth : methodList) { if(meth.getName().equals(methodToken)) { matchingMethods.add(meth); } } // check for a matching method signature for( Method meth : matchingMethods) { // get the types of the arguments the method under // investigation requires. Class[] paramList = meth.getParameterTypes(); // make sure the signature has the required number of // elements. If not, this is not the correct method. if(paramList.length != args.length) { continue; } // Now check if each method argument is assignable from the // type given by the user provided arguments. This means // that we are checking to see if each of the user // arguments is the same as, or is a superclass or // superinterface of the type found in the method signature //(ie it is legal to pass the user arguments to this // method.) If one does not match, then this is not the // correct method and we continue to the next one. boolean signatureMatch = false; for ( int i = 0; i < paramList.length; ++i) { if(paramList[i].isAssignableFrom( argTypes[i] ) ) { signatureMatch = true; } else { continue; } } // if we matched the signature on a matchingly named // method, then we set the method m, and indicate // that we have found a match so that we can stop // marching up the inheritance hierarchy. (ie the // containing loop will terminate. if(true == signatureMatch) { m = meth; matchFound = true; break; } } // move up one level in class hierarchy. c = c.getSuperclass(); } while(null != c && false == matchFound); } catch( /*errors*/) { // do stuff } } // check that m got assigned if(null == m) { System.out.println("From DO: unable to match method"); return false; } // try to invoke the method !!!! try { m.invoke(obj, args); } catch ( /* errors */ ) { // do stuff } 

Hope this ever helps someone!

+6
source share
2 answers

You need to follow the same process as described in the Java Language Specification, Section 15.12, β€œMethod Call Expressions,” to search for the same method that will be found at compile time. In short, it is harder than you think.

A simple option would be to test all methods with the correct name (and do not forget about the methods of all superclasses). For each of these methods, check if all your arguments are compatible with the corresponding method parameter. This may not be perfect, but it works in most cases.

[Update:] The "simple option" fails if there are several overloaded methods in the class. Here is a sample code you can play with:

 package so7691729; import static org.junit.Assert.*; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Map; import java.util.Set; import org.junit.Test; import com.google.common.collect.Maps; import com.google.common.collect.Sets; public class MethodCaller { private boolean isCompatible(Method m, Object... args) { Class<?>[] parameterTypes = m.getParameterTypes(); if (parameterTypes.length == args.length) { for (int i = 0; i < args.length; i++) { if (args[i] != null) { if (!parameterTypes[i].isAssignableFrom(args[i].getClass())) { // TODO: make primitive types equivalent to their boxed types. return false; } } } } else { // TODO: maybe handle varargs methods here return false; } return true; } public Object call1(String fullyQualifiedMethodName, Object obj, Object... args) throws ClassNotFoundException, IllegalAccessException, InvocationTargetException { int lastDot = fullyQualifiedMethodName.lastIndexOf("."); String className = fullyQualifiedMethodName.substring(0, lastDot); String methodName = fullyQualifiedMethodName.substring(lastDot + 1); Class<?> clazz = Class.forName(className); for (Class<?> c = clazz; c != null; c = c.getSuperclass()) { Set<String> sameNameMethods = Sets.newTreeSet(); Map<String, Method> compatibleMethods = Maps.newTreeMap(); for (Method method : c.getDeclaredMethods()) { if (method.getName().equals(methodName)) { sameNameMethods.add(method.toString()); if (isCompatible(method, args)) { compatibleMethods.put(method.toString(), method); } } } if (compatibleMethods.size() > 1) { throw new IllegalArgumentException("Multiple candidates: " + compatibleMethods.keySet()); } if (compatibleMethods.size() == 1) { return compatibleMethods.values().iterator().next().invoke(obj, args); } if (!sameNameMethods.isEmpty()) { throw new IllegalArgumentException("Incompatible types for " + sameNameMethods); } } throw new IllegalArgumentException("No method found."); } public Object call(String fullyQualifiedMethodName, Object obj, Object... args) { try { return call1(fullyQualifiedMethodName, obj, args); } catch (ClassNotFoundException e) { throw new IllegalArgumentException(e); } catch (IllegalAccessException e) { throw new IllegalArgumentException(e); } catch (InvocationTargetException e) { throw new IllegalArgumentException(e); } } public String str(Object obj) { return "object " + obj; } public String str(String str) { return "string " + str; } public int add(int a, int b) { return a + b; } @SuppressWarnings("boxing") public int addObj(Integer a, Integer b) { return a + b; } private void assertCallingError(String msg, String methodName, Object obj, Object... args) { try { call(methodName, obj, args); fail(); } catch (IllegalArgumentException e) { assertEquals(msg, e.getMessage()); } } @SuppressWarnings("boxing") @Test public void test() { MethodCaller dummy = new MethodCaller(); assertEquals("object 1", call("so7691729.MethodCaller.str", dummy, 1)); assertCallingError("Multiple candidates: " + // "[public java.lang.String so7691729.MethodCaller.str(java.lang.Object), " + // "public java.lang.String so7691729.MethodCaller.str(java.lang.String)]", // "so7691729.MethodCaller.str", dummy, "str"); assertCallingError("Incompatible types for [public int so7691729.MethodCaller.add(int,int)]", "so7691729.MethodCaller.add", dummy, 3, 4); assertEquals(7, call("so7691729.MethodCaller.addObj", dummy, 3, 4)); assertCallingError("Incompatible types for [public int so7691729.MethodCaller.addObj(java.lang.Integer,java.lang.Integer)]", "so7691729.MethodCaller.addObj", dummy, "hello", "world"); } } 

And perhaps the specification or implementation of Java Beans has something for you. They may have had the same problem. Or look at Rhino, a JavaScript implementation in pure Java. It allows you to call Java methods directly from JavaScript code, so this is very similar to your problem.

+5
source

3) find a method based on argument classes

You ask the class: "Do you have a method with exactly this signature?" The class says, "No!" You do not specify a "class", do you have something that I can name with these parameters? "As already mentioned, this is not easy to answer as soon as overloaded methods are inherited, and therefore the full API Reflection does not solve this problem.

However: You are not the first to use the helpful answer to the second question. Perhaps MethodUtils.invokeMethod or any brother from the Apache Commons Beanutils project is right for you.

+2
source

Source: https://habr.com/ru/post/898856/


All Articles