这些Java教程是针对JDK 8编写的。本页中描述的示例和实践不利用后续版本引入的改进,并且可能使用不再可用的技术。
请参阅Java语言更改,了解Java SE 9及后续版本中更新的语言功能的摘要。
请参阅JDK发行说明,了解所有JDK发行版中新功能、增强功能以及已删除或已弃用选项的信息。
这个部分包含了使用反射来定位、调用或获取方法信息时开发者可能遇到的问题的示例。
这个示例
展示了当搜索一个类中的特定方法时,如果没有考虑到类型擦除会发生什么。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();
但是并没有找到任何方法。
$ 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); // 解决方法 Object o = m.invoke(ac); // IllegalAccessException // 产品代码应该更优雅地处理这些异常 } 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); // 正常工作 break; case 1: m.invoke(mtt, null); // 正常工作(预计编译器警告) break; case 2: Object arg2 = null; m.invoke(mtt, arg2); // IllegalArgumentException break; case 3: m.invoke(mtt, new Object[0]); // 正常工作 break; case 4: Object arg4 = new Object[0]; m.invoke(mtt, arg4); // IllegalArgumentException break; default: System.out.format("找不到测试%n"); } // 生成代码应该更优雅地处理这些异常 } catch (Exception x) { x.printStackTrace(); } } }
$ java MethodTroubleToo 0 PONG!
由于Method.invoke()
的所有参数都是可选的,除了第一个参数外,当要调用的方法没有参数时,可以省略这些参数。
$ java MethodTroubleToo 1 PONG!
在这种情况下,代码会生成此编译器警告,因为null
是模棱两可的。
$ javac MethodTroubleToo.java MethodTroubleToo.java:16: 警告: 使用不精确的参数类型对可变参数方法的非变长调用; m.invoke(mtt, null); // 正常工作(预计编译器警告) ^ 为可变参数调用进行对象转型 为非变长调用进行对象数组转型以消除此警告 1 警告
无法确定null
是表示空参数数组还是表示第一个参数为null
。
$ java MethodTroubleToo 2 java.lang.IllegalArgumentException: 参数数量错误 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() 失败: 我不能喝负数的液体