文档

Java™ 教程
隐藏目录
调用方法
导航:反射API
课程:成员
章节:方法

调用方法

反射提供了一种在类上调用方法的方式。通常,只有在非反射代码中无法将类的实例强制转换为所需类型时才需要这样做。使用 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,因为Localefinal的:

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

上一页:检索和解析方法修饰符
下一页:故障排除