# 故障排除

> 原文： [https://docs.oracle.com/javase/tutorial/reflect/member/methodTrouble.html](https://docs.oracle.com/javase/tutorial/reflect/member/methodTrouble.html)

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

## NoSuchMethodException 由于类型擦除

[``MethodTrouble`` ](example/MethodTrouble.java)示例说明了在类中搜索特定方法的代码未考虑类型擦除时会发生什么。

```
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`](https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html) 。因此，当代码搜索`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()`](https://docs.oracle.com/javase/8/docs/api/java/lang/Class.html#getMethod-java.lang.String-java.lang.Class...-) 搜索的参数类型必须完全匹配。

* * *

**Tip:** Always pass the upper bound of the parameterized type when searching for a method.

* * *

## 调用方法时出现 IllegalAccessException

如果尝试调用`private`或其他不可访问的方法，则抛出 [`IllegalAccessException`](https://docs.oracle.com/javase/8/docs/api/java/lang/IllegalAccessException.html) 。

[``MethodTroubleAgain`` ](example/MethodTroubleAgain.java)示例显示了典型的堆栈跟踪，这是因为尝试在另一个类中调用私有方法。

```
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`](https://docs.oracle.com/javase/8/docs/api/java/lang/reflect/AccessibleObject.html) which provides the ability to suppress this check via [`AccessibleObject.setAccessible()`](https://docs.oracle.com/javase/8/docs/api/java/lang/reflect/AccessibleObject.html#setAccessible-boolean-). If it succeeds, then subsequent invocations of this method object will not fail due to this problem.

* * *

## Method.invoke（）中的 IllegalArgumentException

[`Method.invoke()`](https://docs.oracle.com/javase/8/docs/api/java/lang/reflect/Method.html#invoke-java.lang.Object-java.lang.Object...-) 已被改装为可变方法。这是一个巨大的便利，但它可能导致意外的行为。 [``MethodTroubleToo`` ](example/MethodTroubleToo.java)实例显示 [`Method.invoke()`](https://docs.oracle.com/javase/8/docs/api/java/lang/reflect/Method.html#invoke-java.lang.Object-java.lang.Object...-) 可产生混淆结果的各种方式。

```
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()`](https://docs.oracle.com/javase/8/docs/api/java/lang/reflect/Method.html#invoke-java.lang.Object-java.lang.Object...-) 的所有参数除了第一个之外都是可选的，因此当要调用的方法没有参数时，可以省略它们。

```
$ 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`](https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html) 并且`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`](https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html) 中，则将其视为 [`Object`](https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html) 。这失败的原因与案例 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`](https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html). 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`](https://docs.oracle.com/javase/8/docs/api/java/lang/reflect/InvocationTargetException.html) 包装调用方法对象时产生的所有异常（已检查和未检查）。 [``MethodTroubleReturns`` ](example/MethodTroubleReturns.java)示例显示了如何检索被调用方法抛出的原始异常。

```
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`](https://docs.oracle.com/javase/8/docs/api/java/lang/reflect/InvocationTargetException.html) 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()`](https://docs.oracle.com/javase/8/docs/api/java/lang/reflect/InvocationTargetException.html#getCause--). This exception does not indicate a problem with the reflection package or its usage.

* * *