Java 教程是为 JDK 8 编写的。本页中描述的示例和实践未利用在后续版本中引入的改进。
反射提供了在类上调用方法的方式。通常,只有在无法在非反射代码中将类的实例强制转换为所需类型时才需要这样做。使用 java.lang.reflect.Method.invoke()
调用方法。第一个参数是要在其上调用此特定方法的对象实例。(如果方法是 static
,则第一个参数应为 null
。)后续参数是方法的参数。如果底层方法抛出异常,它将被 java.lang.reflect.InvocationTargetException
包装。可以使用异常链机制的 InvocationTargetException.getCause()
方法获取方法的原始异常。
考虑一个测试套件,它使用反射来调用给定类中的私有测试方法。
示例在类中搜索 Deet
public
方法,该方法以字符串 "test
" 开始,具有布尔返回类型,以及单个 Locale
参数。然后它调用每个匹配方法。
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("invoking %s()%n", mname); try { m.setAccessible(true); Object o = m.invoke(t, new Locale(args[1], args[2], args[3])); out.format("%s() returned %b%n", mname, (Boolean) o); // Handle any exceptions thrown by method to be invoked. } catch (InvocationTargetException x) { Throwable cause = x.getCause(); err.format("invocation of %s failed: %s%n", mname, cause.getMessage()); } } // production code should handle these exceptions more gracefully } 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 invoking testDeet() Locale = Japanese (Japan,JP), ISO Language Code = jpn testDeet() returned true
$ java Deet Deet xx XX XX invoking testDeet() invocation of testDeet failed: Couldn't find 3-letter language code for xx
首先,请注意只有 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("invoking %s.main()%n", c.getName()); main.invoke(null, (Object)mainArgs); // production code should handle these exceptions more gracefully } catch (ClassNotFoundException x) { x.printStackTrace(); } catch (NoSuchMethodException x) { x.printStackTrace(); } catch (IllegalAccessException x) { x.printStackTrace(); } catch (InvocationTargetException x) { x.printStackTrace(); } } }
首先,为了找到 main()
方法,代码搜索名为“main”的类,带有单个 String
数组参数。由于 main()
是 static
,Method.invoke()
的第一个参数是 null
。第二个参数是要传递的参数数组。
$ java InvokeMain Deet Deet ja JP JP invoking Deet.main() invoking testDeet() Locale = Japanese (Japan,JP), ISO Language Code = jpn testDeet() returned true