使用java反射将派生对象传递给想要超类的方法?
Posted
技术标签:
【中文标题】使用java反射将派生对象传递给想要超类的方法?【英文标题】:Pass derived object into method wanting superclass using java reflection? 【发布时间】:2011-12-03 06:03:46 【问题描述】:编辑:我不清楚。我必须使用反射,因为我是从命令行解释的。我正在做与我提供的代码示例等效的反射。
希望这不是重复,因为这似乎是每天都想做的事情。
我有一个 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);
我所做的(使用反射)是:
-
获取作为函数参数的对象。
获取参数的类
根据参数的类查找方法。
调用方法,传入参数 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 作为参数类型传入时)
编辑:
我将发布我自己的解决方案,以防有人希望看到一种快速破解的方法来执行 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
希望它对某人有所帮助!
【问题讨论】:
这应该可以工作。如果B
是从A
派生的,则它可以在任何需要A
对象的地方使用。这就是多态性的意义所在。
问题是,没有方法doSomething(B)
,只有doSomething(A)
。既然没有完美匹配的方法,代码怎么知道它应该调用那个方法?
注意:仔细阅读问题 - 它是关于如何通过反射找到正确的方法。
我必须使用反射,因为我正在制作命令行解释器。
很久以前我不得不这样做,我们最终遍历对象参数的继承树,直到找到匹配的签名。这适用于一个论点,但对于多个论点来说是淫秽的。请注意,这是在 JDK 1.2 的日子里,我绝对不建议将它用于现代 Java 编码。
【参考方案1】:
您需要遵循 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 Beans 规范或实现对您有所帮助。他们可能有同样的问题需要解决。或者看看 Rhino,一个纯 Java 的 javascript 实现。它允许您直接从 JavaScript 代码调用 Java 方法,因此这与您的问题非常相似。
【讨论】:
+1,很好的答案。我记得 JINI(现在的 Apache River)某处的实现。 谢谢。我想我希望 Method.invoke 会检查参数的超类并验证它们是否符合方法签名的标准。这似乎比将查找负担强加给程序员更容易。您能否解释一下您建议的查找解决方案有时不够用的情况? 我刚刚在我的答案中添加了一些内容。 看看org.mozilla.javascript.NativeJavaMethod.findFunction(Context, MemberBox[], Object[])
。它可能正是您想要的。
哇,感谢您为代码解决了所有麻烦。我现在正在实现自己的搜索功能。我会将我所做的与您的代码进行比较,并查看 org.mozilla 代码。再次感谢。在我接受答案之前,我会先让问题悬而未决。【参考方案2】:
3) 根据参数的类查找方法
你是在问全班同学:“你有没有正是这个签名的方法?”班级说“不!”你不是问“班级,你有什么我可以用这些参数调用的吗?”如前所述,一旦涉及到继承和重载方法,这并不容易回答,因此完整的反射 API 无法解决这个问题。
但是:您不是第一个想要第二个问题可用答案的人。也许MethodUtils.invokeMethod
或 Apache Commons Beanutils 项目中的任何兄弟姐妹都适合您。
【讨论】:
以上是关于使用java反射将派生对象传递给想要超类的方法?的主要内容,如果未能解决你的问题,请参考以下文章