1. 概述

本文将快速介绍如何使用Java反射API在运行时调用方法。反射是Java的强大特性,但使用时需注意性能开销和安全性问题。

2. 准备工作

先创建一个用于演示的简单类:

public class Operations {
    public double publicSum(int a, double b) {
        return a + b;
    }

    public static double publicStaticMultiply(float a, long b) {
        return a * b;
    }

    private boolean privateAnd(boolean a, boolean b) {
        return a && b;
    }

    protected int protectedMax(int a, int b) {
        return a > b ? a : b;
    }
}

3. 获取Method对象

要调用方法,首先需要获取对应的Method对象。Class对象提供了两种主要方式:

3.1. getMethod()

✅ 用于获取公共方法(包括继承的父类方法)
参数:方法名 + 参数类型列表

Method sumInstanceMethod
  = Operations.class.getMethod("publicSum", int.class, double.class);

Method multiplyStaticMethod
  = Operations.class.getMethod(
    "publicStaticMultiply", float.class, long.class);

3.2. getDeclaredMethod()

✅ 用于获取类中声明的所有方法(包括private/protected)
❌ 不包括继承的方法
参数:方法名 + 参数类型列表

Method andPrivateMethod
  = Operations.class.getDeclaredMethod(
    "privateAnd", boolean.class, boolean.class);

Method maxProtectedMethod
  = Operations.class.getDeclaredMethod("protectedMax", int.class, int.class);

4. 调用方法

获取Method对象后,通过invoke()执行方法:

4.1. 实例方法

调用实例方法时,invoke()的第一个参数必须是方法所属类的实例

@Test
public void givenObject_whenInvokePublicMethod_thenCorrect() {
    Method sumInstanceMethod
      = Operations.class.getMethod("publicSum", int.class, double.class);

    Operations operationsInstance = new Operations();
    Double result
      = (Double) sumInstanceMethod.invoke(operationsInstance, 1, 3);

    assertThat(result, equalTo(4.0));
}

4.2. 静态方法

静态方法不需要实例,第一个参数传null即可:

@Test
public void givenObject_whenInvokeStaticMethod_thenCorrect() {
    Method multiplyStaticMethod
      = Operations.class.getDeclaredMethod(
        "publicStaticMultiply", float.class, long.class);

    Double result
      = (Double) multiplyStaticMethod.invoke(null, 3.5f, 2);

    assertThat(result, equalTo(7.0));
}

5. 方法可访问性

⚠️ 默认情况下,反射方法受Java访问控制限制
直接调用private/protected方法会抛出IllegalAccessException

@Test(expected = IllegalAccessException.class)
public void givenObject_whenInvokePrivateMethod_thenFail() {
    Method andPrivateMethod
      = Operations.class.getDeclaredMethod(
        "privateAnd", boolean.class, boolean.class);

    Operations operationsInstance = new Operations();
    Boolean result
      = (Boolean) andPrivateMethod.invoke(operationsInstance, true, false);

    assertFalse(result);
}

@Test(expected = IllegalAccessException.class)
public void givenObject_whenInvokeProtectedMethod_thenFail() {
    Method maxProtectedMethod
      = Operations.class.getDeclaredMethod(
        "protectedMax", int.class, int.class);

    Operations operationsInstance = new Operations();
    Integer result
      = (Integer) maxProtectedMethod.invoke(operationsInstance, 2, 4);
    
    assertThat(result, equalTo(4));
}

5.1. AccessibleObject#setAccessible()

✅ 调用setAccessible(true)绕过访问控制检查
简单粗暴但有效:

@Test
public void givenObject_whenInvokePrivateMethod_thenCorrect() throws Exception {
    Method andPrivatedMethod = Operations.class.getDeclaredMethod("privateAnd", boolean.class, boolean.class);
    andPrivatedMethod.setAccessible(true);  // 关键操作

    Operations operationsInstance = new Operations();
    Boolean result = (Boolean) andPrivatedMethod.invoke(operationsInstance, true, false);

    assertFalse(result);
}

5.2. AccessibleObject#canAccess()

Java 9新增方法,用于检查调用者是否有权访问反射方法
替代已废弃的isAccessible()

@Test
public void givenObject_whenInvokePrivateMethod_thenCheckAccess() throws Exception {
    Operations operationsInstance = new Operations();
    Method andPrivatedMethod = Operations.class.getDeclaredMethod("privateAnd", boolean.class, boolean.class);
    boolean isAccessEnabled = andPrivatedMethod.canAccess(operationsInstance);
 
    assertFalse(isAccessEnabled); // 默认无访问权限
 }

5.3. AccessibleObject#trySetAccessible()

✅ 更优雅的访问控制方式:

  • 成功时返回true
  • 失败时返回false(不像setAccessible()抛异常)
@Test
public void givenObject_whenInvokePublicMethod_thenEnableAccess() throws Exception {
    Operations operationsInstance = new Operations();
    Method andPrivatedMethod = Operations.class.getDeclaredMethod("privateAnd", boolean.class, boolean.class);
    boolean success = andPrivatedMethod.trySetAccessible(); // 尝试开启访问
    
    assertTrue(success); // 验证是否成功开启
    boolean isAccessEnabled = andPrivatedMethod.canAccess(operationsInstance);
    assertTrue(isAccessEnabled);
}

6. 总结

本文演示了通过反射调用实例方法和静态方法的核心流程,重点解决了访问控制问题:

  1. 获取Method对象:getMethod() vs getDeclaredMethod()
  2. 调用方法:invoke()的实例/静态方法差异
  3. 访问控制:setAccessible()canAccess()trySetAccessible()的使用场景

⚠️ 反射虽强大,但需注意:

  • 性能开销比直接调用高
  • 破坏封装性,可能引发安全问题
  • 在模块化系统(JPMS)中可能受限

完整示例代码可在GitHub仓库查看。


原始标题:Call Methods at Runtime Using Java Reflection