我想用
Class.getMethod(String name, Class... parameterTypes)
找到我需要使用给定参数调用的方法,但显然如Bug 6176992中所述,Java不包括那里的自动装箱。因此,如果我的反射类有一个带有(String,int)签名的方法,你仍然会得到一个带有{String.class,Integer.class}数组作为参数的NoSuchMethodException。
这有什么解决方法吗?我能想到的唯一方法是使用基本和非基本类型的每个排列来调用getMethod(),这是我真的不想做的。
编辑:为了更清楚:我很清楚原始类型类,但我不知道它们如何帮助解决我的问题。我的parameterTypes数组来自某个地方,我知道它只会返回非原始类型。我不能假设接口只会用原始类型声明,这正是我的问题:
public interface TestInterface()
{
public void doTest(Integer i1, int i2, double d3, Double d);
}
Class<?>[] classes = { Integer.class, Integer.class, Double.class, Double.class }
// Due to autoboxing I should become the doTest method here, but it doesn't work
TestInterface.class.getMethod("doTest", classes);
答案 0 :(得分:11)
正如@Stephen C所提到的,你唯一的希望就是自己做搜索。他的所有注意事项都有,但我认为只要呼叫者知道警告就会有一点点灵活性来覆盖大部分陷阱......而不是让你的来电者总是特别痛苦。
对于实际执行此类操作的代码,您可以在此处查看: http://meta-jb.svn.sourceforge.net/viewvc/meta-jb/trunk/dev/src/main/java/org/progeeks/util/MethodIndex.java?revision=3811&view=markup
findMethod()调用是入口点,但它委托(在一些缓存之后等)此方法:
private Method searchForMethod( String name, Class[] parms ) {
Method[] methods = type.getMethods();
for( int i = 0; i < methods.length; i++ ) {
// Has to be named the same of course.
if( !methods[i].getName().equals( name ) )
continue;
Class[] types = methods[i].getParameterTypes();
// Does it have the same number of arguments that we're looking for.
if( types.length != parms.length )
continue;
// Check for type compatibility
if( InspectionUtils.areTypesCompatible( types, parms ) )
return methods[i];
}
return null;
}
InspectionUtils.areTypesCompatible()接受两个类型列表,规范化它们的基元,然后验证一个是“可分配”到另一个。因此,它将处理你有一个Integer的情况,并试图调用一个接受int的方法,以及你有一个String的情况,并试图调用一个接受Object的方法。它不处理具有int并调用浮点数的方法的情况。必须有一些特异性。
一点需要注意的是,上述方法只是按方法顺序搜索,因此如果存在歧义,则选择是任意的。到目前为止,我从未遇到过现实世界的问题。
以下是兼容性检查以供参考: public static boolean areTypesCompatible(Class [] targets,Class [] sources){ if(targets.length!= sources.length) 返回false;
for( int i = 0; i < targets.length; i++ ) {
if( sources[i] == null )
continue;
if( !translateFromPrimitive( targets[i] ).isAssignableFrom( sources[i] ) )
return false;
}
return( true );
}
代码是BSD和我的,所以这些代码段是合法使用的。如果您决定直接使用此util包,则最新的公开发布在此处: https://meta-jb.svn.sourceforge.net/svnroot/meta-jb/trunk/dev/m2-repo/org/meta-jb/meta-jb-util/0.17.1/
我只提到这一点,因为很长一段时间没有捆绑下载,因为我的大多数活跃用户都是maven用户。我似乎更喜欢编写代码而不是削减完整版本。 ;)
答案 1 :(得分:10)
如果你有版本&gt; = 2.5的commons-lang,你可以使用MethodUtils.getMatchingAccessibleMethod(...)来处理拳击类型问题。
答案 2 :(得分:4)
是的,您需要使用Integer.TYPE
或(等效)int.class
。
更新:“我的parameterTypes数组来自某个地方,我知道它只会返回非原始类型。”好吧,那就是这个“某个地方”的问题。如果他们没有给你他们想要的方法的正确签名,那么你将如何找到它?如果有两个重载方法,只有其中一个方法不同,需要一个原语而另一个需要包装类?那么它选择哪一个?我的意思是,如果你真的别无选择那么我想你可以循环遍历所有方法,找一个正确名称的方法,并手动检查所有参数类型是否正确,或者是原始的等价物。 / p>
答案 3 :(得分:2)
Integer.class表示Integer对象类型。 Integer.TYPE表示int基本类型。这有用吗?
答案 4 :(得分:2)
反射方法查找并不像编译器那样复杂。我明白为什么你需要这样的东西。假设在运行时你有一个方法名称和一个对象数组作为参数,你希望反射为你提供基于参数类型的确切方法,但它比这更复杂。例如:
void f(Integer i){..}
void f(int i){...}
当参数是Integer类型时,您可以选择哪一个?一个更棘手的问题:
void f(Integer i, List l){...}
void f(Object o, LinkedList l){...}
编译器有一套规则来根据静态信息选择“最具体的方法”;如果它无法确定它会马上提醒你。
您必须模拟编译器并编写算法以找出“最具体的方法”。 (哦,考虑到自动拳击,差点忘了!)
答案 5 :(得分:1)
目前唯一的答案是编写代码来模拟Java编译器的类型提升规则,反过来选择最合适的方法。自动装箱和拆箱只是编译器知道的类型促销的例子......
为什么Java反射API不能这样做?我可以想到很多原因。
在Java 1.5之前,getMethod
班级和朋友不了解如何(例如)推广int
到float
。如果1.5之前没有需要,为什么现在呢?
添加这种东西会使反射方法调用更慢。
自动装箱和拆箱可能会与非反射方法调用混淆。添加反射只会增加更多的混淆。
促销规则的运行时实现将添加一类需要映射到异常并由用户代码诊断的新运行时错误情况。这些将是特别棘手的。例如,它必须处理模糊方法调用的反射等价物。
向后兼容性的首要要求意味着Sun必须将其作为新方法实现。它们无法改变当前方法的行为,因为这可能会破坏数千个客户的现有应用程序。
最后一点与OP的用例有关(如上所述)。 OP表示他的代码不知道是否期望(例如)目标类上具有int
或Integer
参数的方法。假设编写目标类的人提供了重载......并且语义是巧妙地(或不完整地)不同的?无论你做什么,都会有OP的代码选择客户端不期望的过载的情况。坏。
IMO,OP的代码最好强加一些简单的API规则,说明何时使用原语和包装器是正确的。
答案 6 :(得分:0)
您可以在反射调用周围添加一些额外的逻辑,尝试将您的Integer.class
(或其他)转换为相应的基本类,然后重复查找该方法,直到您获得匹配。如果您有Apache Commons Lang,那么wrapperToPrimitive方法将为您进行此对话,但自己编写它是微不足道的。
getMethod
不优雅,对于具有大量原始参数的方法,它甚至可能很慢。
答案 7 :(得分:0)
尝试使用Class.isPrimitive()
确定它是否是基本类型,如果是,则使用反射来检索TYPE
字段并检查它是否相等。所以,在非常自由的伪代码中:
for(Method m:getDeclaredMethods())
for(Class c:m.getParameterTypes() && Class desired:desiredMethodArgTypes)
if(c.isAssignableFrom(desired))
//matches
if(c.isPrimitive() && c==desired.getDeclaredField("TYPE").get(desiredObject))
//matches