Передать производный объект в метод, желающий суперкласс, используя отражение java?

EDIT: я не был ясен. Я должен использовать отражение, потому что я перевела из командной строки. Я делаю эквивалент отражения примеров кода, которые я предоставил.

надеюсь, что это не дубликат, так как это кажется повседневной вещью, чтобы хотеть сделать.

у меня есть класс A и класс B, который расширяет A. Если у меня есть метод в классе C, такой как public void doSomething(a), как я могу использовать отражение для передачи объекта B в эту функцию? Я хочу сделать (отражение) эквивалент:

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

то, что я сделал (используя отражение), это:

  1. получить объекты, которые являются аргументами функции.
  2. получить классы аргументов
  3. найдите метод, основанный на классах аргументов.
  4. вызовите метод, передав в аргументе объекты.

Это отлично работает, если бы я собирался передать объект A В C. doSomething(...). Однако, если я я пытаюсь передать объект B В C. doSomething(...) он терпит неудачу на Шаге 3, с этой ошибкой:

java.ленг.NoSuchMethodException: C. doSomething (B)

каков подходящий способ заставить C. doSomething признать, что B является A? (при поиске метода с помощью getDeclaredMethod (имя строки, класс... parameterTypes) и передача B.class в качестве параметра типа)

EDIT:

я опубликую свое собственное решение в случае, если кто-то хочет увидеть один быстро взломанный способ сделать то, что предложил Роланд Иллиг. В этом примере я ссылаюсь на эти готовые переменные:

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

так...

    //*** 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's provided arguments. This means
                    // that we are checking to see if each of the user's 
                    // arguments is the same as, or is a superclass or 
                    // superinterface of the type found in the method signature
                    //(i.e. 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. (i.e. 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
    }

надеюсь, что это поможет кому-нибудь!

2 ответов


вам нужно следовать тому же процессу, что и в спецификации языка Java, раздел 15.12 "выражения вызова метода", для поиска того же метода, который будет найден во время компиляции. Короче, это сложнее, чем вы думаете.

простым вариантом было бы проверить все методы с правильным именем (и не забудьте методы всех суперклассов). Для каждого из этих методов проверьте, является ли все ваши аргументы назначение-совместимость с соответствующим параметром метода. Это может быть не идеально, но работает в большинстве случаев.

[Update:]" простой вариант " терпит неудачу, когда в классе есть несколько перегруженных методов. Вот пример кода, с которым вы можете играть:

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

}

и, возможно, спецификация или реализация Java Beans имеет что-то для вас. Возможно, им нужно было решить ту же проблему. Или посмотрите на Rhino, реализацию JavaScript в чистой Java. Это позволяет вызов методов Java непосредственно из кода JavaScript, так что очень похоже на вашу проблему.


3) Найдите метод, основанный на классах аргументов

вы спрашиваете класс: "у вас есть какой-либо метод с ровно это подпись? Класс говорит: "Нет!"Вы не спрашивая " класс, у вас есть что-то, что я могу назвать с этими параметрами?"Как уже упоминалось, это нелегко ответить, как только задействованы методы наследования и перегрузки, и поэтому API полного отражения не решает эту проблему вопрос.

однако: вы не первый, кто хочет полезный ответ на второй вопрос. Возможно,MethodUtils.invokeMethod или любой брат из проекта Apache Commons Beanutils подходит для вас.