Skip to content

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()

性能优化

反射调用比直接调用慢,原因在于每次调用都要经过:

  1. 安全检查(权限验证)
  2. 参数类型检查
  3. 方法分派

优化一:设置 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&lt;?&gt; 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&lt;?&gt; srcClass = source.getClass();
        Class&lt;?&gt; 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&lt;?&gt; clazz) throws Exception {
        return new PropertyDescriptor[0];  // 简化实现
    }
}

这个简化版的 BeanCopier 展示了反射调用的典型模式:通过方法名动态查找方法,然后调用。

要点回顾

  1. invoke 第一个参数是对象 — 静态方法传 null
  2. 返回值是 Object — 需要强制类型转换,void 方法返回 null
  3. 异常被包装 — 方法内部的异常通过 InvocationTargetException 抛出
  4. 性能开销大 — 可以通过 setAccessible(true) 和缓存优化
  5. 泛型被擦除 — 无法通过泛型类型区分重载方法

Method.invoke() 是反射的核心——它让「运行时决定调用什么」成为可能。这是 Java 动态性的根基。

基于 VitePress 构建