Java 王者修炼手册【基础篇 - 反射机制】:反射让底层操控力直接拉满!

今天咱们把反射这个 Java 里的 "灵活刺客" 盘明白了 —— 它不像基础语法那样按部就班,而是能 "绕后突破",直接操控类的底层成员,就像王者里的兰陵王,能隐身摸到后排,出其不意。
大家好,我是程序员强子。
今天我们来练习一下底层操控力极高的【英雄】:反射
反射的 【基础技能包】
Class 类对象的获取以及其用法
Constructor 类及其用法
Field 类及其用法
Method 类及其用法
反射的【连招顺序】
先通过 Class 对象 【锁定目标】
再用 Method/Field/Constructor 类 【等大招,先做准备,走位、摸眼或者闪现进场,准备开打】
最后 method.invoke()【释放技能,combo 连招组合拳】
发车啦~ 来不及解释了~
Class 类
Class 类对象的获取
通过对象的 getClass()方法(需已有实例),
适用于已知对象实例的场景,调用对象的 getClass()方法返回其对应类的 Class 对象。
通过类名.class(无需实例)
适用于已知类名的场景,直接通过类名.class 获取
通过 Class.forName(String className)(运行时加载,常用)
适用于类名通过字符串动态指定的场景(如配置文件加载类),需要处理 ClassNotFoundException。
示例:
getName()、getCanonicalName()与 getSimpleName()的区别
getName()
返回类的全限定名(包含包名),数组 / 内部类有特殊格式(JVM 规范格式)
普通类:com.example.Test;
数组 Test[]:[Lcom.example.Test;
getCanonicalName()
返回类的规范全限定名(更易读),数组格式为类型[]
普通类:com.example.Test;
数组 Test[]:com.example.Test[]
getSimpleName()
返回类的简单名称(不含包名),内部类仅显示内部类名
普通类:Test;
数组 Test[]:Test[]
getFields()和 getDeclaredFields()的区别
getFields()
获取当前类及所有父类中被 public 修饰的字段(公有字段)
例子:如果 User 类有 public 字段 name,其父类 Person 有 public 字段 age,那么 User.class.getFields()会同时返回 name 和 age
getDeclaredFields()
获取当前类自己声明的所有字段(不管权限,public、private、protected、默认权限都算),但不包括父类的任何字段(哪怕父类的字段是 public)
例子:User 类有 public 字段 name 和 private 字段 password,其父类 Person 有 public 字段 age,那么 User.class.getDeclaredFields()只会返回 name 和 password,不会返回 age。
一句话总结区别:
getFields() = 公有字段(自己的 + 父类的);
getDeclaredFields() = 自己的所有字段(不限权限,不含父类的)
Constructor 类
Constructor 类代表类的构造方法,通过 Class 对象的方法获取,可用于动态创建对象。
核心方法
getConstructor(Class<?>... parameterTypes):获取 public 构造方法(参数类型匹配)。
getDeclaredConstructor(Class<?>... parameterTypes):获取类中所有访问修饰符的构造方法(包括 private)。
newInstance(Object... initargs):通过构造方法创建实例(参数为构造方法的入参)。
setAccessible(boolean flag):设置是否忽略访问权限检查(用于访问 private 构造方法)。
示例
定义一个 Person 对象,该对象有两个私有字段,并且有两个构造方法,其中一个是私有的无参构造,另外一个是 public 的有参构造
获取 public 有参构造并创建实例 && 获取 private 无参构造并创建实例(需忽略访问权限)
Field 类
Field 类代表类的成员变量(字段),通过 Class 对象的方法获取,可用于动态读写字段值。
核心方法
getField(String name):获取 public 字段(包括父类的 public 字段)。
getDeclaredField(String name):获取类中所有访问修饰符的对应 name 的字段(仅本类,不含父类)。
getDeclaredFields():获取所表示的类或接口的所有(包含 private 修饰的)字段,不包括继承的字段
set(Object obj, Object value):为对象 obj 的当前字段设置值(静态字段 obj 可为 null)。
get(Object obj):获取对象 obj 的当前字段值(静态字段 obj 可为 null)。
setAccessible(boolean flag):忽略访问权限检查(用于访问 private 字段)。
示例
还是以 Person 类为例子,修改其中一个字段 name 为 public
操作 public 字段(name) && 操作 private 字段(age)
Method 类
Method 类代表类的方法,通过 Class 对象的方法获取,可用于动态调用方法。
核心方法
getMethod(String name, Class<?>... parameterTypes):获取 public 方法(包括父类的 public 方法)。
getDeclaredMethod(String name, Class<?>... parameterTypes):获取类中所有访问修饰符的方法(仅本类,不含父类)。
invoke(Object obj, Object... args):调用对象 obj 的当前方法(参数为方法入参,静态方法 obj 可为 null)。
setAccessible(boolean flag):忽略访问权限检查(用于访问 private 方法)。
示例
还是以 Person 类为例子,增加了两个方法,一个公有的 public 方法,一个是 private 方法
调用 public 方法(sayHello) && 调用 private 方法(getInfo)
反射机制原理
执行流程
先定目标:明确你要 “操作” 什么
先想清楚反射的目的:是要动态创建对象(实例化)?还是读写字段值(比如改个私有属性)?或是调用某个方法(哪怕是私有方法)?
就像王者里先确定 “这波要开龙” 还是 “抓边”,目标清晰了才好选技能。
起手式:获取 Class 对象,选对 “入口”
不管干啥,第一步都得拿到 Class 对象(反射的 “总控制台”),三种方式按需选:
已知实例:用对象.getClass()(比如 user.getClass()),像 “从英雄本体查信息”;
已知类名(字符串):用 Class.forName("全类名")(比如加载数据库驱动),像 “远程召唤英雄”;
已知具体类:用类名.class(比如 User.class),像 “直接锁定英雄模板”。
选技能:按需求挑 API,分清 “权限” 和 “范围”
根据目标选对应的 API,核心是分清 “公有 / 私有”“自己的 / 父类的”:
实例化对象:用 Constructor 类。公有构造器用 getConstructor(参数类型),私有构造器用 getDeclaredConstructor(参数类型)(记得用 setAccessible(true)破权限);
操作字段:用 Field 类。公有字段(含父类)用 getField("字段名"),自己的所有字段(含私有,不含父类)用 getDeclaredField("字段名");
调用方法:用 Method 类。公有方法(含父类)用 getMethod("方法名", 参数类型),自己的所有方法(含私有,不含父类)用 getDeclaredMethod("方法名", 参数类型)。
放技能:执行操作,完成目标最后一步直接执行:
实例化:Constructor 调用 newInstance(参数值);
改字段:Field 调用 set(对象, 新值),读字段用 get(对象);
调方法:Method 调用 invoke(对象, 参数值)。
(注意:操作私有成员时,必须先 setAccessible(true)解除权限限制,否则会 “技能空放” 报错。)
反射的线程安全设计
Java 反射 API 在设计时严格保证了线程安全性,主要依赖两方面机制:
缓存的线程安全访问
Class 类中存储反射元信息的 reflectionData(包含方法、字段、构造器等缓存)通过 volatile 修饰 + CAS 操作保证可见性和原子性。
不可变的元信息载体
反射获取的 Method、Field、Constructor 对象本身是不可变的(其内部存储的方法名、参数类型等元信息一旦创建就无法修改)
即使多个线程共享这些对象,也不会出现线程安全问题(唯一可修改的 accessible 标志通过 volatile 修饰,保证读写可见性)
确保多线程环境下反射操作的稳定性(如框架中并发反射调用 Bean 方法时不会出现异常)
软引用(SoftReference)缓存 class 元信息
Class 类内部通过
private transient SoftReference<ReflectionData<T>> reflectionData
缓存反射元信息(ReflectionData 是一个内部类,包含 methods、fields、constructors 等数组)
软引用的特性是 “内存充足时保留,内存不足时被 JVM 回收”。反射元信息(如方法列表)属于高频访问但非核心数据,用软引用缓存可在减少重复解析元信息开销的同时,避免内存泄漏(若用强引用,即使类已无用,缓存仍可能占用内存)
当软引用被回收(reflectionData.get() == null)时,反射方法会重新从 JVM 的方法区(元空间)解析类元信息,重建 ReflectionData 并重新缓存
大幅降低反射操作的性能开销(解析类元信息是耗时操作),同时适应 JVM 的内存管理策略
方法 / 字段 / 构造器的拷贝机制(数据隔离)
当通过 getMethod()、getField()等方法获取反射对象时,JVM 返回的是元信息的拷贝实例,而非内部缓存的原始数组元素。
避免反射操作中的交叉污染,保证多线程 / 多场景下反射对象的独立性
版权声明: 本文为 InfoQ 作者【DonaldCen】的原创文章。
原文链接:【http://xie.infoq.cn/article/8bb104f005f029de305009993】。文章转载请联系作者。







评论