在Java开发中,尤其是编写框架或工具类时,反射(Reflection)是一个强大但又容易混淆的工具。其中,Class 对象的 getMethods()getDeclaredMethods() 方法是获取类方法的两个常用入口,但它们的行为差异巨大,经常成为初学者的困惑点。

今天,我们就来彻底厘清这两者的区别,让你在需要时能够毫不犹豫地做出正确选择。

一、核心区别速览


如果你赶时间,下面这张表和一句话总结可以让你快速了解核心差异:

| 特性 | getMethods() | getDeclaredMethods() |
|---|---|---|
| 查找范围 | 当前类 + 所有父类 + 实现的接口 | 仅当前类 |
| 访问修饰符 | 只返回 public 方法 | 返回所有方法 (public, protected, package-private, private) |
| 继承关系 | 包含继承来的 public 方法 | 不包含任何继承来的方法 |

> 一句话总结:
>
> getMethods(): 获取一个类的 “公共API全家桶”(自己和祖辈的所有 public 方法)。
> getDeclaredMethods(): 获取一个类的 “私房清单”(仅自己声明的所有方法,无视权限)。

二、代码实战:眼见为实


理论说千遍,不如代码跑一遍。让我们通过一个生动的例子来感受它们的区别。

1. 准备实验材料(类结构)


我们创建一个包含接口、父类和子类的完整结构:

// 接口
interface Flyable {
    void fly(); // 默认是 public abstract
}

// 父类
class Animal {
    public void eat() {
        System.out.println("Animal eats");
    }

    protected void breathe() {
        System.out.println("Animal breathes");
    }

    private void secret() {
        System.out.println("Animal's secret");
    }
}

// 子类 (我们的主角)
class Bird extends Animal implements Flyable {
    public void sing() {
        System.out.println("Bird sings");
    }

    private void nest() {
        System.out.println("Bird builds a nest");
    }

    // 重写接口方法
    @Override
    public void fly() {
        System.out.println("Bird flies");
    }

    // 重写父类方法
    @Override
    public void eat() {
        System.out.println("Bird eats seeds");
    }
}


2. 开始实验


现在,我们用反射来分别检查 Bird 类:

import java.lang.reflect.Method;

public class ReflectionTest {
    public static void main(String[] args) {
        Class<Bird> birdClass = Bird.class;

        System.out.println("----------- 1. 使用 getMethods() on Bird.class -----------");
        // 获取所有公共方法,包括从父类和接口继承的
        Method[] methods = birdClass.getMethods();
        for (Method method : methods) {
            System.out.println(method.getName() + " (声明于: " + method.getDeclaringClass().getSimpleName() + ")");
        }

        System.out.println("n----------- 2. 使用 getDeclaredMethods() on Bird.class -----------");
        // 只获取在 Bird 类中明确定义的方法,无论访问权限如何
        Method[] declaredMethods = birdClass.getDeclaredMethods();
        for (Method method : declaredMethods) {
            // 打印方法名和它的访问修饰符
            System.out.println(method.getName() + " (修饰符: " + java.lang.reflect.Modifier.toString(method.getModifiers()) + ")");
        }
    }
}


3. 结果分析


**getMethods() 的输出 (公共API全家桶):**

----------- 1. 使用 getMethods() on Bird.class -----------
sing (声明于: Bird)
fly (声明于: Bird)
eat (声明于: Bird)
wait (声明于: Object)
wait (声明于: Object)
wait (声明于: Object)
equals (声明于: Object)
toString (声明于: Object)
hashCode (声明于: Object)
getClass (声明于: Object)
notify (声明于: Object)
notifyAll (声明于: Object)


**分析:**

- 自己的 public: sing(), fly(), eat() 都被找到了。
- 父类的 public: Animal 类没有自己的 public 方法被直接继承(eat被重写),但如果有一个未被重写的 public 方法,也会在这里出现。
- 接口的 public: fly() 来自 Flyable 接口,被 Bird 实现,所以也包含在内。
- 祖先类的 public: 所有类都继承自 Object,因此 Object 类的所有 public 方法(如 wait, equals, hashCode 等)也全部被找到。
- 被排除的: Animal 的 protected 方法 breathe() 和 private 方法 secret() 没有出现。

**getDeclaredMethods() 的输出 (私房清单):**

----------- 2. 使用 getDeclaredMethods() on Bird.class -----------
eat (修饰符: public)
sing (修饰符: public)
fly (修饰符: public)
nest (修饰符: private)


**分析:**

- 只看自己: 列表里只有在 Bird.java 文件中明确写下的4个方法。
- 无视权限: 无论是 public 的 sing 还是 private 的 nest,都被找到了。
- 无视继承: 从 Animal 继承的 breathe() 和从 Object 继承的 wait() 等方法,一个都没有出现。

三、应用场景:我该用哪个?


理解了区别,选择就变得简单了。

场景一:使用 getMethods()


当你需要模拟**正常的外部调用**,获取一个类的完整公共API时,用它。

- 动态调用: 比如,根据用户输入或配置文件中的方法名(如setTenantId)来动态调用一个对象的setter。这个setter可能定义在父类中,使用 getMethods() 才能找到。
- 框架集成: Spring进行依赖注入、Jackson进行JSON序列化时,它们需要发现并调用对象的 public getter/setter 和其他公共方法。

**你的例子**: // 检查是否存在getTenantId和setTenantId方法(包括继承的方法)
这个需求的目标是找到可用的方法,不管它是在子类还是父类定义的。因此,getMethods() 是完美的选择。

场景二:使用 getDeclaredMethods()


当你需要**深入分析一个类的内部结构**,无视访问权限时,用它。

- 单元测试: 使用Mockito等工具时,有时需要操作或验证一个类的私有方法。
- 代码分析工具: 静态代码分析器、代码覆盖率工具需要检查一个类中定义的所有方法,包括私有的。
- ORM框架: 像Hibernate这类框架,为了直接操作实体属性,可能会通过反射访问私有字段或其对应的私有getter/setter。

**你的例子**: // 检查是否存在getTenantId和setTenantId方法
如果你的意图是**严格检查当前这个类本身**是否定义了这两个方法,而不关心父类,那么 getDeclaredMethods() 是正确的。

四、总结


最后,让我们用一个简单的口诀来结束今天的学习:

- getMethods = 公开的 (public) + 全家的 (含继承)
- getDeclaredMethods = 自己的 (declared) + 全部的 (所有修饰符)

记住这个口诀,下次在IDE中敲下 arg.getClass().get... 时,你将胸有成竹!