Java 教程是为 JDK 8 编写的。本页中描述的示例和实践未利用在后续版本中引入的改进。
本节包含开发人员在使用反射来查找,调用或获取有关方法的信息时可能遇到的问题的示例。
示例说明了在类中搜索特定方法的代码未考虑类型擦除时会发生什么。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()
搜索的参数类型必须完全匹配。
如果尝试调用 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)
private
方法和单独的私有类中的公共方法。)但是,Method
被声明为继承 AccessibleObject
,它提供了通过 AccessibleObject.setAccessible()
来抑制此检查的功能。如果成功,则此方法对象的后续调用不会因此问题而失败。
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]
创建一个空数组,而对于可变参数方法,这相当于不传递任何可选参数。
$ 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()
不期望参数。
foo(Object... o)
时,编译器会将传递给 foo()
的所有参数放入 Object
类型的数组。foo()
的实现与声明 foo(Object[] o)
的实现相同。理解这可能有助于避免上述问题的类型。
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