在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序列化时,它们需要发现并调用对象的
publicgetter/setter 和其他公共方法。
你的例子: // 检查是否存在getTenantId和setTenantId方法(包括继承的方法)
这个需求的目标是找到可用的方法,不管它是在子类还是父类定义的。因此,getMethods() 是完美的选择。
场景二:使用 getDeclaredMethods()
当你需要深入分析一个类的内部结构,无视访问权限时,用它。
- 单元测试: 使用Mockito等工具时,有时需要操作或验证一个类的私有方法。
- 代码分析工具: 静态代码分析器、代码覆盖率工具需要检查一个类中定义的所有方法,包括私有的。
- ORM框架: 像Hibernate这类框架,为了直接操作实体属性,可能会通过反射访问私有字段或其对应的私有getter/setter。
你的例子: // 检查是否存在getTenantId和setTenantId方法
如果你的意图是严格检查当前这个类本身是否定义了这两个方法,而不关心父类,那么 getDeclaredMethods() 是正确的。
四、总结
最后,让我们用一个简单的口诀来结束今天的学习:
getMethods= 公开的 (public) + 全家的 (含继承)getDeclaredMethods= 自己的 (declared) + 全部的 (所有修饰符)
记住这个口诀,下次在IDE中敲下 arg.getClass().get... 时,你将胸有成竹!