Troubleshooting
本节包含开发人员在使用反射来查找,调用或获取有关方法的信息时可能遇到的问题的示例。
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:
搜索方法时,请始终传递参数化类型的上限。
调用方法时发生 IllegalAccessException
如果try调用private
或其他无法访问的方法,则抛出IllegalAccessException。
MethodTroubleAgain示例显示了典型的堆栈跟踪,该堆栈跟踪是由于try在另一个类中调用私有方法而导致的。
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:
存在访问限制,该访问限制防止通常无法通过直接调用访问的方法的反射调用。 (这包括---但不限于--单独的类中的private
方法和单独的私有类中的公共方法.)但是,声明Method
扩展AccessibleObject,从而可以通过AccessibleObject.setAccessible()抑制此检查。如果成功,则此方法对象的后续调用不会由于此问题而失败。
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:
声明方法foo(Object... o)
时,编译器会将传递给foo()
的所有参数放入类型Object的数组中。 foo()
的实现与声明为foo(Object[] o)
的实现相同。理解这一点可能有助于避免上述类型的问题。
调用方法失败时, 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:
如果抛出InvocationTargetException,则调用该方法。问题的诊断将与直接调用该方法并抛出由getCause()检索到的异常一样。此异常并不表示反射包或其用法有问题。