2011-10-07 19 views
6

編輯:我不清楚。我必須使用反射,因爲我正在從命令行解釋。我正在做相當於我提供的代碼示例的反思。將派生對象傳遞給想要使用java反射的超類的方法?

希望這不是重複的,因爲它似乎是每天要做的事情。

我有一個類A和一個擴展了A的類B.如果我在類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(字符串名稱,類... parameterTypes)和通過B.class作爲參數類型的方法時)

編輯:

的情況下,有人想我會後我自己的解決方案看到一個很快被黑了的方式來做Roland Illig建議的事情。在這個例子中,我引用這些預先製作的變量:

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 
    } 

希望這將幫助別人的某個時候!

+0

這應該是工作。如果'B'是從'A'派生出來的,那麼它可以用在任何期望'A'對象的地方。這就是*多態*的全部內容。 –

+0

事情是,沒有方法'doSomething(B)',只有'doSomething(A)'。代碼如何知道它應該調用該方法,因爲完全匹配的方法不可用? –

+1

注:仔細閱讀問題 - 這是關於如何找到正確的方法與反思。 –

回答

5

您需要遵循Java語言規範第15.12節「方法調用表達式」中所述的相同過程,以找到在編譯時可以找到的相同方法。總之,這比你想象的更復雜。

一個簡單的變體是用正確的名字檢查所有的方法(並且不要忘記所有超類的方法)。對於這些方法中的每一種,請檢查您的參數的全部是否與指定的方法參數分配兼容。這可能不完美,但在大多數情況下都適用。

[更新:]當一個類中有多個重載方法時,「簡單變體」失敗。以下是您可以使用的一些示例代碼:

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 Bean規範或實現對您有幫助。他們可能有同樣的問題需要解決。或者看看Rhino,這是一個純Java中的JavaScript實現。它允許您直接從JavaScript代碼調用Java方法,所以這與您的問題非常相似。

+0

+1,很好的答案。我記得在JINI(現在是Apache River)的某個地方的一個實現。 –

+0

謝謝。我想我希望Method.invoke能夠檢查參數的超類,並驗證它們是否符合方法簽名的標準。看起來這會比給程序員強加查找負擔更容易。你可以解釋一下,當你建議的查找解決方案不足時? – user487100

+0

我剛添加了一些東西給我的答案。 –

2

3) look up the method based upon the classes of the arguments

你問類:「你有此簽名正是任何方法」班級說「不!」你是而不是問「Class,你有什麼我可以用這些參數調用?」如前所述,只要涉及繼承和重載方法,就不容易回答,因此完整的Reflection API不能解決此問題。

但是:您不是第一個想要第二個問題的可用答案的人。也許MethodUtils.invokeMethod或來自Apache Commons Beanutils項目的任何兄弟都適合你。