故障排除
原文: https://docs.oracle.com/javase/tutorial/reflect/member/methodTrouble.html
本节包含开发人员在使用反射来查找,调用或获取有关方法的信息时可能遇到的问题的示例。
NoSuchMethodException 由于类型擦除
MethodTrouble
示例说明了在类中搜索特定方法的代码未考虑类型擦除时会发生什么。
import java.lang.reflect.Method;
public class MethodTrouble<T> {
public void lookup(T t) {}
public void find(Integer i) {}
public static void main(String... args) {
try {
String mName = args[0];
Class cArg = Class.forName(args[1]);
Class<?> c = (new MethodTrouble<Integer>()).getClass();
Method m = c.getMethod(mName, cArg);
System.out.format("Found:%n %s%n", m.toGenericString());
// production code should handle these exceptions more gracefully
} catch (NoSuchMethodException x) {
x.printStackTrace();
} catch (ClassNotFoundException x) {
x.printStackTrace();
}
}
}
$ java MethodTrouble lookup java.lang.Integer
java.lang.NoSuchMethodException: MethodTrouble.lookup(java.lang.Integer)
at java.lang.Class.getMethod(Class.java:1605)
at MethodTrouble.main(MethodTrouble.java:12)
$ java MethodTrouble lookup java.lang.Object
Found:
public void MethodTrouble.lookup(T)
当使用泛型参数类型声明方法时,编译器将使用其上限替换泛型类型,在这种情况下,T
的上限是 Object
。因此,当代码搜索lookup(Integer)
时,没有找到任何方法,尽管MethodTrouble
的实例创建如下:
Class<?> c = (new MethodTrouble<Integer>()).getClass();
搜索lookup(Object)
按预期成功。
$ java MethodTrouble find java.lang.Integer
Found:
public void MethodTrouble.find(java.lang.Integer)
$ java MethodTrouble find java.lang.Object
java.lang.NoSuchMethodException: MethodTrouble.find(java.lang.Object)
at java.lang.Class.getMethod(Class.java:1605)
at MethodTrouble.main(MethodTrouble.java:12)
在这种情况下,find()
没有通用参数,因此 getMethod()
搜索的参数类型必须完全匹配。
Tip: Always pass the upper bound of the parameterized type when searching for a method.
调用方法时出现 IllegalAccessException
如果尝试调用private
或其他不可访问的方法,则抛出 IllegalAccessException
。
MethodTroubleAgain
示例显示了典型的堆栈跟踪,这是因为尝试在另一个类中调用私有方法。
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
class AnotherClass {
private void m() {}
}
public class MethodTroubleAgain {
public static void main(String... args) {
AnotherClass ac = new AnotherClass();
try {
Class<?> c = ac.getClass();
Method m = c.getDeclaredMethod("m");
// m.setAccessible(true); // solution
Object o = m.invoke(ac); // IllegalAccessException
// production code should handle these exceptions more gracefully
} catch (NoSuchMethodException x) {
x.printStackTrace();
} catch (InvocationTargetException x) {
x.printStackTrace();
} catch (IllegalAccessException x) {
x.printStackTrace();
}
}
}
抛出异常的堆栈跟踪如下。
$ java MethodTroubleAgain
java.lang.IllegalAccessException: Class MethodTroubleAgain can not access a
member of class AnotherClass with modifiers "private"
at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:65)
at java.lang.reflect.Method.invoke(Method.java:588)
at MethodTroubleAgain.main(MethodTroubleAgain.java:15)
Tip: An access restriction exists which prevents reflective invocation of methods which normally would not be accessible via direct invocation. (This includes—-but is not limited to—-private
methods in a separate class and public methods in a separate private class.) However, Method
is declared to extend AccessibleObject
which provides the ability to suppress this check via AccessibleObject.setAccessible()
. If it succeeds, then subsequent invocations of this method object will not fail due to this problem.
Method.invoke()中的 IllegalArgumentException
Method.invoke()
已被改装为可变方法。这是一个巨大的便利,但它可能导致意外的行为。 MethodTroubleToo
实例显示 Method.invoke()
可产生混淆结果的各种方式。
import java.lang.reflect.Method;
public class MethodTroubleToo {
public void ping() { System.out.format("PONG!%n"); }
public static void main(String... args) {
try {
MethodTroubleToo mtt = new MethodTroubleToo();
Method m = MethodTroubleToo.class.getMethod("ping");
switch(Integer.parseInt(args[0])) {
case 0:
m.invoke(mtt); // works
break;
case 1:
m.invoke(mtt, null); // works (expect compiler warning)
break;
case 2:
Object arg2 = null;
m.invoke(mtt, arg2); // IllegalArgumentException
break;
case 3:
m.invoke(mtt, new Object[0]); // works
break;
case 4:
Object arg4 = new Object[0];
m.invoke(mtt, arg4); // IllegalArgumentException
break;
default:
System.out.format("Test not found%n");
}
// production code should handle these exceptions more gracefully
} catch (Exception x) {
x.printStackTrace();
}
}
}
$ java MethodTroubleToo 0
PONG!
由于 Method.invoke()
的所有参数除了第一个之外都是可选的,因此当要调用的方法没有参数时,可以省略它们。
$ java MethodTroubleToo 1
PONG!
这种情况下的代码会生成此编译器警告,因为null
不明确。
$ javac MethodTroubleToo.java
MethodTroubleToo.java:16: warning: non-varargs call of varargs method with
inexact argument type for last parameter;
m.invoke(mtt, null); // works (expect compiler warning)
^
cast to Object for a varargs call
cast to Object[] for a non-varargs call and to suppress this warning
1 warning
无法确定null
是表示空数组参数还是null
的第一个参数。
$ java MethodTroubleToo 2
java.lang.IllegalArgumentException: wrong number of arguments
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke
(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke
(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at MethodTroubleToo.main(MethodTroubleToo.java:21)
尽管参数是null
,但由于类型是 Object
并且ping()
完全没有参数,因此失败。
$ java MethodTroubleToo 3
PONG!
这是因为new Object[0]
创建一个空数组,而对于 varargs 方法,这相当于不传递任何可选参数。
$ java MethodTroubleToo 4
java.lang.IllegalArgumentException: wrong number of arguments
at sun.reflect.NativeMethodAccessorImpl.invoke0
(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke
(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke
(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at MethodTroubleToo.main(MethodTroubleToo.java:28)
与前面的示例不同,如果空数组存储在 Object
中,则将其视为 Object
。这失败的原因与案例 2 失败的原因相同,ping()
不期望参数。
Tip: When a method foo(Object... o)
is declared the compiler will put all of the arguments passed to foo()
in an array of type Object
. The implementation of foo()
is the same as if it were declared foo(Object[] o)
. Understanding this may help avoid the types of problems illustrated above.
调用方法失败时的 InvocationTargetException
InvocationTargetException
包装调用方法对象时产生的所有异常(已检查和未检查)。 MethodTroubleReturns
示例显示了如何检索被调用方法抛出的原始异常。
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class MethodTroubleReturns {
private void drinkMe(int liters) {
if (liters < 0)
throw new IllegalArgumentException("I can't drink a negative amount of liquid");
}
public static void main(String... args) {
try {
MethodTroubleReturns mtr = new MethodTroubleReturns();
Class<?> c = mtr.getClass();
Method m = c.getDeclaredMethod("drinkMe", int.class);
m.invoke(mtr, -1);
// production code should handle these exceptions more gracefully
} catch (InvocationTargetException x) {
Throwable cause = x.getCause();
System.err.format("drinkMe() failed: %s%n", cause.getMessage());
} catch (Exception x) {
x.printStackTrace();
}
}
}
$ java MethodTroubleReturns
drinkMe() failed: I can't drink a negative amount of liquid
Tip: If an InvocationTargetException
is thrown, the method was invoked. Diagnosis of the problem would be the same as if the method was called directly and threw the exception that is retrieved by getCause()
. This exception does not indicate a problem with the reflection package or its usage.