故障排除

原文: https://docs.oracle.com/javase/tutorial/reflect/member/methodTrouble.html

本节包含开发人员在使用反射来查找,调用或获取有关方法的信息时可能遇到的问题的示例。

NoSuchMethodException 由于类型擦除

MethodTrouble 示例说明了在类中搜索特定方法的代码未考虑类型擦除时会发生什么。

  1. import java.lang.reflect.Method;
  2. public class MethodTrouble<T> {
  3. public void lookup(T t) {}
  4. public void find(Integer i) {}
  5. public static void main(String... args) {
  6. try {
  7. String mName = args[0];
  8. Class cArg = Class.forName(args[1]);
  9. Class<?> c = (new MethodTrouble<Integer>()).getClass();
  10. Method m = c.getMethod(mName, cArg);
  11. System.out.format("Found:%n %s%n", m.toGenericString());
  12. // production code should handle these exceptions more gracefully
  13. } catch (NoSuchMethodException x) {
  14. x.printStackTrace();
  15. } catch (ClassNotFoundException x) {
  16. x.printStackTrace();
  17. }
  18. }
  19. }
  1. $ java MethodTrouble lookup java.lang.Integer
  2. java.lang.NoSuchMethodException: MethodTrouble.lookup(java.lang.Integer)
  3. at java.lang.Class.getMethod(Class.java:1605)
  4. at MethodTrouble.main(MethodTrouble.java:12)
  1. $ java MethodTrouble lookup java.lang.Object
  2. Found:
  3. public void MethodTrouble.lookup(T)

当使用泛型参数类型声明方法时,编译器将使用其上限替换泛型类型,在这种情况下,T的上限是 Object 。因此,当代码搜索lookup(Integer)时,没有找到任何方法,尽管MethodTrouble的实例创建如下:

  1. Class<?> c = (new MethodTrouble<Integer>()).getClass();

搜索lookup(Object)按预期成功。

  1. $ java MethodTrouble find java.lang.Integer
  2. Found:
  3. public void MethodTrouble.find(java.lang.Integer)
  4. $ java MethodTrouble find java.lang.Object
  5. java.lang.NoSuchMethodException: MethodTrouble.find(java.lang.Object)
  6. at java.lang.Class.getMethod(Class.java:1605)
  7. 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 示例显示了典型的堆栈跟踪,这是因为尝试在另一个类中调用私有方法。

  1. import java.lang.reflect.InvocationTargetException;
  2. import java.lang.reflect.Method;
  3. class AnotherClass {
  4. private void m() {}
  5. }
  6. public class MethodTroubleAgain {
  7. public static void main(String... args) {
  8. AnotherClass ac = new AnotherClass();
  9. try {
  10. Class<?> c = ac.getClass();
  11. Method m = c.getDeclaredMethod("m");
  12. // m.setAccessible(true); // solution
  13. Object o = m.invoke(ac); // IllegalAccessException
  14. // production code should handle these exceptions more gracefully
  15. } catch (NoSuchMethodException x) {
  16. x.printStackTrace();
  17. } catch (InvocationTargetException x) {
  18. x.printStackTrace();
  19. } catch (IllegalAccessException x) {
  20. x.printStackTrace();
  21. }
  22. }
  23. }

抛出异常的堆栈跟踪如下。

  1. $ java MethodTroubleAgain
  2. java.lang.IllegalAccessException: Class MethodTroubleAgain can not access a
  3. member of class AnotherClass with modifiers "private"
  4. at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:65)
  5. at java.lang.reflect.Method.invoke(Method.java:588)
  6. 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() 可产生混淆结果的各种方式。

  1. import java.lang.reflect.Method;
  2. public class MethodTroubleToo {
  3. public void ping() { System.out.format("PONG!%n"); }
  4. public static void main(String... args) {
  5. try {
  6. MethodTroubleToo mtt = new MethodTroubleToo();
  7. Method m = MethodTroubleToo.class.getMethod("ping");
  8. switch(Integer.parseInt(args[0])) {
  9. case 0:
  10. m.invoke(mtt); // works
  11. break;
  12. case 1:
  13. m.invoke(mtt, null); // works (expect compiler warning)
  14. break;
  15. case 2:
  16. Object arg2 = null;
  17. m.invoke(mtt, arg2); // IllegalArgumentException
  18. break;
  19. case 3:
  20. m.invoke(mtt, new Object[0]); // works
  21. break;
  22. case 4:
  23. Object arg4 = new Object[0];
  24. m.invoke(mtt, arg4); // IllegalArgumentException
  25. break;
  26. default:
  27. System.out.format("Test not found%n");
  28. }
  29. // production code should handle these exceptions more gracefully
  30. } catch (Exception x) {
  31. x.printStackTrace();
  32. }
  33. }
  34. }
  1. $ java MethodTroubleToo 0
  2. PONG!

由于 Method.invoke() 的所有参数除了第一个之外都是可选的,因此当要调用的方法没有参数时,可以省略它们。

  1. $ java MethodTroubleToo 1
  2. PONG!

这种情况下的代码会生成此编译器警告,因为null不明确。

  1. $ javac MethodTroubleToo.java
  2. MethodTroubleToo.java:16: warning: non-varargs call of varargs method with
  3. inexact argument type for last parameter;
  4. m.invoke(mtt, null); // works (expect compiler warning)
  5. ^
  6. cast to Object for a varargs call
  7. cast to Object[] for a non-varargs call and to suppress this warning
  8. 1 warning

无法确定null是表示空数组参数还是null的第一个参数。

  1. $ java MethodTroubleToo 2
  2. java.lang.IllegalArgumentException: wrong number of arguments
  3. at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
  4. at sun.reflect.NativeMethodAccessorImpl.invoke
  5. (NativeMethodAccessorImpl.java:39)
  6. at sun.reflect.DelegatingMethodAccessorImpl.invoke
  7. (DelegatingMethodAccessorImpl.java:25)
  8. at java.lang.reflect.Method.invoke(Method.java:597)
  9. at MethodTroubleToo.main(MethodTroubleToo.java:21)

尽管参数是null,但由于类型是 Object 并且ping()完全没有参数,因此失败。

  1. $ java MethodTroubleToo 3
  2. PONG!

这是因为new Object[0]创建一个空数组,而对于 varargs 方法,这相当于不传递任何可选参数。

  1. $ java MethodTroubleToo 4
  2. java.lang.IllegalArgumentException: wrong number of arguments
  3. at sun.reflect.NativeMethodAccessorImpl.invoke0
  4. (Native Method)
  5. at sun.reflect.NativeMethodAccessorImpl.invoke
  6. (NativeMethodAccessorImpl.java:39)
  7. at sun.reflect.DelegatingMethodAccessorImpl.invoke
  8. (DelegatingMethodAccessorImpl.java:25)
  9. at java.lang.reflect.Method.invoke(Method.java:597)
  10. 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 示例显示了如何检索被调用方法抛出的原始异常。

  1. import java.lang.reflect.InvocationTargetException;
  2. import java.lang.reflect.Method;
  3. public class MethodTroubleReturns {
  4. private void drinkMe(int liters) {
  5. if (liters < 0)
  6. throw new IllegalArgumentException("I can't drink a negative amount of liquid");
  7. }
  8. public static void main(String... args) {
  9. try {
  10. MethodTroubleReturns mtr = new MethodTroubleReturns();
  11. Class<?> c = mtr.getClass();
  12. Method m = c.getDeclaredMethod("drinkMe", int.class);
  13. m.invoke(mtr, -1);
  14. // production code should handle these exceptions more gracefully
  15. } catch (InvocationTargetException x) {
  16. Throwable cause = x.getCause();
  17. System.err.format("drinkMe() failed: %s%n", cause.getMessage());
  18. } catch (Exception x) {
  19. x.printStackTrace();
  20. }
  21. }
  22. }
  1. $ java MethodTroubleReturns
  2. 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.