本教程适用于JDK 8。本页中描述的示例和实践不利用后续版本中引入的改进,并可能使用不再可用的技术。
有关Java SE 9及其后续版本中更新的语言特性的摘要,请参阅Java语言变化。
有关所有JDK版本的新功能、增强功能以及已删除或已弃用选项的信息,请参阅JDK发布说明。
反射提供了一种在类上调用方法的方式。通常,只有在非反射代码中无法将类的实例强制转换为所需类型时才需要这样做。使用 java.lang.reflect.Method.invoke()
方法来调用方法。第一个参数是要在其上调用此特定方法的对象实例。(如果方法是静态的,则第一个参数应为 null
。)后续参数是方法的参数。如果底层方法抛出异常,它将被 java.lang.reflect.InvocationTargetException
包装。可以使用异常链机制的 InvocationTargetException.getCause()
方法检索方法的原始异常。
考虑一个测试套件,它使用反射在给定类中调用私有测试方法。在
示例中,搜索类中以字符串 "Deet
test
" 开头、返回类型为布尔值并具有单个 Locale
参数的 public
方法。然后调用每个匹配的方法。
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Type; import java.util.Locale; import static java.lang.System.out; import static java.lang.System.err; public class Deet<T> { private boolean testDeet(Locale l) { // getISO3Language() may throw a MissingResourceException out.format("Locale = %s, ISO Language Code = %s%n", l.getDisplayName(), l.getISO3Language()); return true; } private int testFoo(Locale l) { return 0; } private boolean testBar() { return true; } public static void main(String... args) { if (args.length != 4) { err.format("Usage: java Deet <classname> <langauge> <country> <variant>%n"); return; } try { Class<?> c = Class.forName(args[0]); Object t = c.newInstance(); Method[] allMethods = c.getDeclaredMethods(); for (Method m : allMethods) { String mname = m.getName(); if (!mname.startsWith("test") || (m.getGenericReturnType() != boolean.class)) { continue; } Type[] pType = m.getGenericParameterTypes(); if ((pType.length != 1) || Locale.class.isAssignableFrom(pType[0].getClass())) { continue; } out.format("调用 %s()%n", mname); try { m.setAccessible(true); Object o = m.invoke(t, new Locale(args[1], args[2], args[3])); out.format("%s() 返回 %b%n", mname, (Boolean) o); // 处理要调用的方法抛出的任何异常。 } catch (InvocationTargetException x) { Throwable cause = x.getCause(); err.format("调用 %s 失败: %s%n", mname, cause.getMessage()); } } // 生产代码应更优雅地处理这些异常 } catch (ClassNotFoundException x) { x.printStackTrace(); } catch (InstantiationException x) { x.printStackTrace(); } catch (IllegalAccessException x) { x.printStackTrace(); } } }
Deet
调用了getDeclaredMethods()
,它将返回类中显式声明的所有方法。此外,使用Class.isAssignableFrom()
来确定定位的方法的参数是否与所需调用兼容。从技术上讲,代码可以测试以下语句是否为true
,因为Locale
是final
的:
Locale.class == pType[0].getClass()
然而,Class.isAssignableFrom()
更通用。
$ java Deet Deet ja JP JP 调用 testDeet() Locale = 日语 (日本,JP), ISO 语言代码 = jpn testDeet() 返回 true
$ java Deet Deet xx XX XX 调用 testDeet() 调用 testDeet 失败: 无法找到 xx 的 3 个字母语言代码
首先注意,只有 testDeet()
满足代码强制执行的声明限制。接下来,当 testDeet()
被传递一个无效的参数时,它会抛出一个未检查的java.util.MissingResourceException
异常。在反射中,对于已检查和未检查的异常处理没有区别。它们都被包装在一个InvocationTargetException
中。
Method.invoke()
可以用于向方法传递可变数量的参数。理解的关键概念是,可变参数的方法被实现为将可变参数打包在数组中。
示例演示了如何调用任何类中的InvokeMain
main()
入口点,并在运行时传递一组参数。
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Arrays; public class InvokeMain { public static void main(String... args) { try { Class<?> c = Class.forName(args[0]); Class[] argTypes = new Class[] { String[].class }; Method main = c.getDeclaredMethod("main", argTypes); String[] mainArgs = Arrays.copyOfRange(args, 1, args.length); System.out.format("调用 %s.main()%n", c.getName()); main.invoke(null, (Object)mainArgs); // 生产代码应该更优雅地处理这些异常 } catch (ClassNotFoundException x) { x.printStackTrace(); } catch (NoSuchMethodException x) { x.printStackTrace(); } catch (IllegalAccessException x) { x.printStackTrace(); } catch (InvocationTargetException x) { x.printStackTrace(); } } }
首先,代码会寻找一个名为"main"的类,该类有一个参数,参数类型是一个数组,数组元素类型是String
。由于main()
是static
方法,所以null
是传递给Method.invoke()
的第一个参数。第二个参数是要传递的参数数组。
$ java InvokeMain Deet Deet ja JP JP 调用 Deet.main() 调用 testDeet() 区域设置 = 日本(日本,JP) ISO 语言代码 = jpn testDeet() 返回 true