Передать производный объект в метод, требующий суперкласса с использованием отражения Java - PullRequest
6 голосов
/ 07 октября 2011

РЕДАКТИРОВАТЬ: Я не был ясен. Я должен использовать отражение, потому что я интерпретирую из командной строки. Я делаю эквивалент отражения примеров кода, которые я предоставил.

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

У меня есть класс A и класс B, который расширяет A. Если у меня есть метод в классе C, такой как public void doSomething (A 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. вызвать метод, передав аргумент Objects.

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

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

Как правильно заставить C.doSomething распознать, что B - это A? (при поиске метода с использованием getDeclaredMethod (String name, Class ... parameterTypes) и передачей B.class в качестве типа параметра)

РЕДАКТИРОВАТЬ:

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

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 ]

5 голосов
/ 07 октября 2011

Вам необходимо выполнить тот же процесс, который описан в Спецификации языка 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 голосов
/ 08 октября 2011

3) поиск метода на основе классов аргументов

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...