Method.invoke() 调用方法
你见过这样的代码吗?
java
method.invoke(obj, arg1, arg2);这行代码看起来平淡无奇,但背后藏着 Java 反射的精髓——在运行时决定调用哪个方法。
不是编译时,不是写代码时,是运行时。
invoke 的基本用法
Method.invoke() 的签名:
java
public Object invoke(Object obj, Object... args) throws IllegalAccessException, InvocationTargetException- 第一个参数:方法所属的对象(静态方法传
null) - 可变参数:方法的实参
- 返回值:方法的返回值(如果没有返回值,返回
null)
调用实例方法
java
public class InvokeInstanceMethod {
static class Greeter {
public String sayHello(String name) {
return "Hello, " + name + "!";
}
public void greet() {
System.out.println("Greetings!");
}
public int calculate(int a, int b) {
return a + b * 2;
}
}
public static void main(String[] args) throws Exception {
Greeter greeter = new Greeter();
Class<?> clazz = greeter.getClass();
// 调用返回 String 的方法
Method helloMethod = clazz.getDeclaredMethod("sayHello", String.class);
String result = (String) helloMethod.invoke(greeter, "World");
System.out.println(result); // Hello, World!
// 调用返回 void 的方法
Method greetMethod = clazz.getDeclaredMethod("greet");
greetMethod.invoke(greeter); // Greetings!(返回值是 null)
// 调用返回 int 的方法
Method calcMethod = clazz.getDeclaredMethod("calculate", int.class, int.class);
int sum = (int) calcMethod.invoke(greeter, 3, 5);
System.out.println("3 + 5*2 = " + sum); // 13
}
}调用静态方法
静态方法不依附于任何对象,调用时第一个参数传 null:
java
public class InvokeStaticMethod {
public static int max(int a, int b) {
return Math.max(a, b);
}
public static String format(String template, Object... args) {
return String.format(template, args);
}
public static void main(String[] args) throws Exception {
Class<?> clazz = InvokeStaticMethod.class;
// 调用静态方法 —— obj 传 null
Method maxMethod = clazz.getDeclaredMethod("max", int.class, int.class);
int maxResult = (int) maxMethod.invoke(null, 10, 20);
System.out.println("max(10, 20) = " + maxResult); // 20
// 调用可变参数方法
Method formatMethod = clazz.getDeclaredMethod("format", String.class, Object[].class);
String formatted = (String) formatMethod.invoke(null, "Value: %d", 42);
System.out.println(formatted); // Value: 42
}
}注意:可变参数方法在编译后会变成数组。String.format(String template, Object... args) 实际上相当于 String.format(String template, Object[] args)。所以反射调用时,需要手动包装成数组。
泛型方法的调用陷阱
泛型是编译时特性,运行时会擦除。这意味着你无法通过泛型类型来区分重载方法。
java
public class GenericMethodPitfall {
// 这两个方法在运行时签名是一样的!
public <T> void print(T value) {
System.out.println("Generic: " + value);
}
public void print(String value) {
System.out.println("String: " + value);
}
public static void main(String[] args) throws Exception {
Class<?> clazz = GenericMethodPitfall.class;
GenericMethodPitfall obj = new GenericMethodPitfall();
// getDeclaredMethod 只认方法签名,不认泛型
// 所以用 Object.class 获取泛型方法
Method genericPrint = clazz.getDeclaredMethod("print", Object.class);
genericPrint.invoke(obj, "hello"); // Generic: hello
// String 版本的优先级更高
Method stringPrint = clazz.getDeclaredMethod("print", String.class);
stringPrint.invoke(obj, "world"); // String: world
}
}异常处理
Method.invoke() 会抛出两类异常:
java
public class InvokeExceptionHandling {
public void riskyMethod() throws IOException {
throw new IOException("Network error");
}
public static void main(String[] args) {
Class<?> clazz = InvokeExceptionHandling.class;
try {
Method method = clazz.getDeclaredMethod("riskyMethod");
method.invoke(new InvokeExceptionHandling());
} catch (IllegalAccessException e) {
// 你没有权限调用这个方法
System.out.println("访问被拒绝: " + e.getMessage());
} catch (InvocationTargetException e) {
// 方法本身抛出的异常,会被包装在这里
Throwable cause = e.getCause();
System.out.println("方法抛出异常: " + cause.getClass().getSimpleName());
System.out.println("异常信息: " + cause.getMessage());
} catch (NoSuchMethodException e) {
System.out.println("方法不存在: " + e.getMessage());
}
}
}关键点:InvocationTargetException 包装了方法内部真正抛出的异常。要获取原异常,调用 e.getCause()。
性能优化
反射调用比直接调用慢,原因在于每次调用都要经过:
- 安全检查(权限验证)
- 参数类型检查
- 方法分派
优化一:设置 accessible
java
public class InvokeOptimization {
public String compute(String input) {
return input.toUpperCase();
}
public static void main(String[] args) throws Exception {
Class<?> clazz = InvokeOptimization.class;
Method method = clazz.getDeclaredMethod("compute", String.class);
// 优化前:每次调用都做安全检查
// method.invoke(new InvokeOptimization(), "test");
// 优化后:跳过安全检查
method.setAccessible(true);
// 预热 JIT
InvokeOptimization obj = new InvokeOptimization();
for (int i = 0; i < 10000; i++) {
method.invoke(obj, "warmup");
}
// 测试
long start = System.nanoTime();
for (int i = 0; i < 100000; i++) {
method.invoke(obj, "test");
}
long elapsed = System.nanoTime() - start;
System.out.println("100k 次调用耗时: " + (elapsed / 1_000_000) + "ms");
}
}优化二:缓存 Method 对象
获取 Method 对象本身也有开销,框架通常会缓存:
java
import java.util.concurrent.*;
public class MethodCachingDemo {
static class Cache {
private static final ConcurrentHashMap<Key, Method> CACHE = new ConcurrentHashMap<>();
static class Key {
private final Class<?> clazz;
private final String name;
private final Class<?>[] paramTypes;
Key(Class<?> clazz, String name, Class<?>[] paramTypes) {
this.clazz = clazz;
this.name = name;
this.paramTypes = paramTypes;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
Key that = (Key) o;
return clazz == that.clazz && name.equals(that.name) &&
Arrays.equals(paramTypes, that.paramTypes);
}
@Override
public int hashCode() {
return Objects.hash(clazz, name, Arrays.hashCode(paramTypes));
}
}
public static Method get(Class<?> clazz, String name, Class<?>... paramTypes) {
return CACHE.computeIfAbsent(
new Key(clazz, name, paramTypes),
k -> {
try {
return clazz.getDeclaredMethod(name, paramTypes);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
);
}
}
public int add(int a, int b) { return a + b; }
public static void main(String[] args) throws Exception {
// 第一次获取:从 HashMap 查找
Method m1 = Cache.get(MethodCachingDemo.class, "add", int.class, int.class);
// 第二次获取:直接命中缓存
Method m2 = Cache.get(MethodCachingDemo.class, "add", int.class, int.class);
System.out.println("是同一个对象吗? " + (m1 == m2)); // true
}
}优化三:MethodHandle(JDK 7+)
MethodHandle 提供了更底层的调用方式,性能更好,但 API 更复杂:
java
import java.lang.invoke.*;
public class MethodHandleDemo {
public String transform(String input) {
return ">> " + input + " <<";
}
public static void main(String[] args) throws Throwable {
Class<?> clazz = MethodHandleDemo.class;
// 创建 MethodHandle
MethodHandle handle = MethodHandles.lookup()
.findVirtual(clazz, "transform", MethodType.methodType(String.class, String.class));
// 调用
MethodHandleDemo obj = new MethodHandleDemo();
String result = (String) handle.invoke(obj, "hello");
System.out.println(result); // >> hello <<
}
}一个实际例子:通用对象拷贝
java
import java.beans.PropertyDescriptor;
import java.lang.reflect.*;
public class BeanCopier {
public static void copy(Object source, Object target) throws Exception {
Class<?> srcClass = source.getClass();
Class<?> tgtClass = target.getClass();
// 获取源对象的所有 getters
for (PropertyDescriptor srcPd : getPropertyDescriptors(srcClass)) {
Method readMethod = srcPd.getReadMethod();
if (readMethod == null) continue;
try {
// 获取目标对象的 setter
PropertyDescriptor tgtPd = new PropertyDescriptor(srcPd.getName(), tgtClass);
Method writeMethod = tgtPd.getWriteMethod();
if (writeMethod == null) continue;
// 调用 getXxx() 获取值
Object value = readMethod.invoke(source);
// 调用 setXxx(value) 设置值
writeMethod.invoke(target, value);
} catch (IntrospectionException e) {
// 目标没有对应的属性,跳过
}
}
}
static PropertyDescriptor[] getPropertyDescriptors(Class<?> clazz) throws Exception {
return new PropertyDescriptor[0]; // 简化实现
}
}这个简化版的 BeanCopier 展示了反射调用的典型模式:通过方法名动态查找方法,然后调用。
要点回顾
- invoke 第一个参数是对象 — 静态方法传
null - 返回值是 Object — 需要强制类型转换,
void方法返回null - 异常被包装 — 方法内部的异常通过
InvocationTargetException抛出 - 性能开销大 — 可以通过
setAccessible(true)和缓存优化 - 泛型被擦除 — 无法通过泛型类型区分重载方法
Method.invoke() 是反射的核心——它让「运行时决定调用什么」成为可能。这是 Java 动态性的根基。
