Java基础全套教程(十)—— 反射机制详解
Java基础全套教程(十)—— 反射机制详解
前言
反射是Java语言极具特色的核心特性,也是Java实现动态编程的基石。日常开发中使用的Spring框架、MyBatis框架、JUnit单元测试、动态代理等核心功能,底层全部依赖反射机制实现。
普通Java代码属于静态编码:编译阶段就固定了类、对象、方法的调用关系,运行时无法修改。而反射打破了编译期的限制,让程序在运行阶段可以动态解析类结构、创建对象、操作属性与方法。本节课将从原理、核心载体、实现方式、实操开发、性能优化、优缺点与实战应用,全方位详解反射机制。
一、反射机制核心概念
1.1 什么是反射
反射(Reflection)是Java提供的运行时自省与操作机制:程序在运行状态下,可以针对任意已加载的类,获取其全部内部结构信息,同时可以动态创建该类的实例、调用任意成员方法、修改任意成员变量。
通俗理解:常规编码是“提前知道类,直接使用类”;反射是“运行时看透未知类,根据类的结构信息动态操作类”,哪怕是类中私有修饰的属性和方法,也可以通过反射突破权限限制进行操作。
1.2 反射的两大核心作用
反射的所有应用场景,都基于两大核心能力延伸而来:
-
RTTI(运行时类型识别):程序运行过程中,动态识别对象真实类型、解析类的修饰符、构造器、属性、方法、父类、接口、注解等全部元信息。
-
DC(动态组件装配):无需编译期绑定代码逻辑,运行时动态加载类、创建对象、调用方法、赋值属性,实现代码解耦与灵活扩展。
1.3 反射的核心价值
反射让Java从“静态固化语言”具备了“动态扩展能力”,核心价值体现在两点:
-
低耦合高灵活:代码无需硬编码绑定具体类和方法,可通过配置、参数动态适配不同类,大幅提升代码通用性。
-
框架底层支撑:几乎所有Java主流框架的核心功能都依赖反射实现,是进阶开发、源码阅读的必备知识点。
二、反射核心原理——Class对象
2.1 Class对象的本质
反射机制的唯一核心载体是 java.lang.Class 类,所有反射操作都必须基于Class对象完成。
当JVM加载一个类时,会在**方法区(元空间)**自动生成一个专属的Class对象,这个对象相当于当前类的“完整说明书”,存储了类的所有结构数据:构造方法、成员变量、成员方法、继承关系、注解信息等。
2.2 Class对象核心特性
-
一个类在JVM整个生命周期中,有且仅有一个Class对象,无论实例化多少个对象,最终指向的Class对象完全一致。
-
基本数据类型、数组、接口、枚举,都会对应专属的Class对象。
-
Class对象是反射的入口,没有获取Class对象,就无法执行任何反射操作。
2.3 类的加载与反射的关联
常规对象创建流程:编译生成class字节码文件 → JVM加载字节码 → 生成Class对象 → new创建实例对象。
反射对象创建流程:编译无需绑定类 → 运行时加载字节码 → 获取Class对象 → 动态创建实例、操作成员。
三、获取Class对象的三种方式(必备基础)
获取Class对象是所有反射操作的前提,Java提供三种标准方式,适配不同开发场景,下文统一使用全新的 User 实体类作为测试载体,全程独立案例,无原版重复代码。
3.1 通用测试实体类
public class User {
// 私有成员变量
private String username;
private Integer age;
// 公共成员变量
public String phone;
// 无参构造方法
public User() {}
// 公共有参构造
public User(String username, Integer age) {
this.username = username;
this.age = age;
}
// 私有有参构造
private User(String phone) {
this.phone = phone;
}
// 普通公共方法
public void sayHello() {
System.out.println("用户名:" + username + ",年龄:" + age);
}
// 私有方法
private void updateInfo(String newName) {
this.username = newName;
System.out.println("用户名已更新为:" + newName);
}
// getter & setter
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
3.2 三种获取Class对象的方式
方式一:实例对象.getClass() (运行时获取)
通过类的实例对象调用原生方法获取Class对象,必须先创建对象,适用于已有实例对象的场景。
public class GetClassByInstance {
public static void main(String[] args) {
// 创建实例对象
User user = new User();
// 调用getClass()获取Class对象
Class<? extends User> clazz = user.getClass();
// 打印类全限定名
System.out.println("类全限定名:" + clazz.getName());
}
}
方式二:类名.class (编译期锁定,高效)
通过静态语法 类名.class 获取,无需创建对象,编译期即可识别,性能最优,适用于明确知道目标类的场景。
public class GetClassByStatic {
public static void main(String[] args) {
// 静态语法获取Class对象
Class<User> clazz = User.class;
System.out.println("类名:" + clazz.getSimpleName());
System.out.println("全限定类名:" + clazz.getName());
}
}
方式三:Class.forName() (动态加载,最常用)
通过Class类静态方法传入类全限定名动态加载类,无需提前知晓类,支持配置化加载,是框架开发中最常用的方式,需捕获编译异常。
public class GetClassByForName {
public static void main(String[] args) throws ClassNotFoundException {
// 传入类的全限定包名+类名
Class<?> clazz = Class.forName("com.reflect.entity.User");
System.out.println("加载的类:" + clazz.getName());
}
}
3.3 三种方式对比总结
-
对象.getClass():依赖实例对象,仅运行时可用,无法提前预判类类型。
-
类名.class:高效、无异常,编译期校验,适合固定类的反射操作。
-
Class.forName():支持动态配置、热加载,灵活性最高,框架核心首选方式。
四、反射核心实操操作
获取Class对象后,可通过Class类提供的API,实现对类中构造器、成员变量、成员方法的全部操作,核心区分两组API:
-
getXXX():仅获取public修饰的公共成员(包含父类公共成员)。
-
getDeclaredXXX():获取当前类所有权限成员(public、private、protected、默认权限,不含父类)。
4.1 反射操作构造方法
常用API说明
| 方法名 | 功能描述 |
|---|---|
| getConstructors() | 获取所有公共构造方法数组 |
| getDeclaredConstructors() | 获取当前类所有权限构造方法数组 |
| getConstructor(参数类型…) | 获取指定参数列表的公共构造方法 |
| getDeclaredConstructor(参数类型…) | 获取指定参数列表的任意权限构造方法 |
实操代码:获取构造器并动态创建对象
import java.lang.reflect.Constructor;
public class ReflectConstructorDemo {
public static void main(String[] args) throws Exception {
Class<User> clazz = User.class;
// 1.获取所有公共构造方法
Constructor<?>[] publicConstructors = clazz.getConstructors();
System.out.println("公共构造方法数量:" + publicConstructors.length);
// 2.获取所有权限构造方法
Constructor<?>[] allConstructors = clazz.getDeclaredConstructors();
System.out.println("所有构造方法数量:" + allConstructors.length);
// 3.通过公共有参构造创建对象
Constructor<User> publicConstructor = clazz.getConstructor(String.class, Integer.class);
User user1 = publicConstructor.newInstance("张三", 22);
user1.sayHello();
// 4.通过私有构造创建对象(需开启权限放行)
Constructor<User> privateConstructor = clazz.getDeclaredConstructor(String.class);
privateConstructor.setAccessible(true); // 关闭权限校验
User user2 = privateConstructor.newInstance("13800138000");
System.out.println("私有构造创建对象手机号:" + user2.phone);
}
}
4.2 反射操作成员变量
常用API说明
| 方法名 | 功能描述 |
|---|---|
| getFields() | 获取所有公共成员变量数组 |
| getDeclaredFields() | 获取当前类所有权限成员变量数组 |
| getField(字段名) | 获取指定名称的公共成员变量 |
| getDeclaredField(字段名) | 获取指定名称的任意权限成员变量 |
实操代码:获取、修改私有/公共字段值
import java.lang.reflect.Field;
public class ReflectFieldDemo {
public static void main(String[] args) throws Exception {
Class<User> clazz = User.class;
User user = clazz.newInstance();
// 1.操作公共字段
Field phoneField = clazz.getField("phone");
phoneField.set(user, "13900139000");
System.out.println("公共手机号:" + phoneField.get(user));
// 2.操作私有字段
Field nameField = clazz.getDeclaredField("username");
nameField.setAccessible(true); // 放行私有权限
nameField.set(user, "李四");
System.out.println("私有用户名:" + nameField.get(user));
}
}
4.3 反射操作成员方法
常用API说明
| 方法名 | 功能描述 |
|---|---|
| getMethods() | 获取所有公共方法(含父类) |
| getDeclaredMethods() | 获取当前类所有权限方法 |
| getMethod(方法名,参数类型…) | 获取指定公共方法 |
| getDeclaredMethod(方法名,参数类型…) | 获取指定任意权限方法 |
实操代码:调用公共、私有方法
import java.lang.reflect.Method;
public class ReflectMethodDemo {
public static void main(String[] args) throws Exception {
Class<User> clazz = User.class;
User user = clazz.getDeclaredConstructor().newInstance();
// 1.调用公共无参方法
Method helloMethod = clazz.getMethod("sayHello");
user.setUsername("王五");
user.setAge(25);
helloMethod.invoke(user);
// 2.调用私有有参方法
Method updateMethod = clazz.getDeclaredMethod("updateInfo", String.class);
updateMethod.setAccessible(true);
updateMethod.invoke(user, "王五更新");
}
}
4.4 获取类的拓展信息
通过Class对象还可以获取类的包名、父类、实现接口、类名等基础拓展信息,常用于通用工具类开发:
public class ReflectClassInfoDemo {
public static void main(String[] args) {
Class<User> clazz = User.class;
// 获取简单类名
System.out.println("简单类名:" + clazz.getSimpleName());
// 获取全限定类名
System.out.println("全限定类名:" + clazz.getName());
// 获取包名
System.out.println("包名:" + clazz.getPackage().getName());
// 获取父类
System.out.println("父类:" + clazz.getSuperclass().getSimpleName());
// 获取实现的接口
Class<?>[] interfaces = clazz.getInterfaces();
for (Class<?> inter : interfaces) {
System.out.println("实现接口:" + inter.getSimpleName());
}
}
}
五、关键方法:setAccessible() 权限放行
5.1 方法作用
setAccessible(boolean flag) 是反射核心优化与权限放行方法,作用是开启/关闭Java权限安全校验:
-
true:关闭安全校验,突破private、protected权限限制,可操作私有成员,同时减少校验开销、提升反射效率。
-
false(默认):开启安全校验,仅可操作public成员,私有成员直接报错。
5.2 使用注意事项
该方法仅对当前反射操作生效,不会修改类的原生权限,属于运行时临时放行,不会破坏代码编译期权限规则。
六、反射性能分析与优化
6.1 反射效率低的原因
反射性能远低于普通直接调用,核心原因:
-
反射运行时需要动态解析字节码文件,解析类结构信息,消耗额外资源。
-
默认开启权限安全校验,增加大量判断逻辑。
-
JVM无法对反射代码做编译期优化、指令重排。
6.2 性能测试对比(全新案例)
循环10万次执行赋值方法,对比普通调用与反射调用的耗时差异:
import java.lang.reflect.Method;
public class ReflectPerformanceTest {
public static void main(String[] args) throws Exception {
User user = new User();
int count = 100000;
// 普通方法调用耗时
long start1 = System.currentTimeMillis();
for (int i = 0; i < count; i++) {
user.setAge(20);
}
long end1 = System.currentTimeMillis();
System.out.println("普通调用耗时:" + (end1 - start1) + "ms");
// 反射方法调用耗时
Class<User> clazz = User.class;
Method setAgeMethod = clazz.getMethod("setAge", Integer.class);
long start2 = System.currentTimeMillis();
for (int i = 0; i < count; i++) {
setAgeMethod.invoke(user, 20);
}
long end2 = System.currentTimeMillis();
System.out.println("反射调用耗时:" + (end2 - start2) + "ms");
// 优化后反射耗时(关闭权限校验)
setAgeMethod.setAccessible(true);
long start3 = System.currentTimeMillis();
for (int i = 0; i < count; i++) {
setAgeMethod.invoke(user, 20);
}
long end3 = System.currentTimeMillis();
System.out.println("优化后反射耗时:" + (end3 - start3) + "ms");
}
}
6.3 反射性能优化方案
-
优先调用
setAccessible(true)关闭安全校验,大幅提升效率。 -
缓存Class对象、Method、Field对象,避免循环中重复获取。
-
高频业务场景尽量少用反射,框架底层、通用工具类场景优先使用。
七、反射优缺点总结
7.1 优点
-
动态灵活:运行时动态操作类,无需编译期绑定,适配配置化、通用化开发。
-
突破权限限制:可操作类中私有成员,实现常规代码无法实现的功能。
-
解耦性强:降低代码硬编码耦合度,提升代码复用性与扩展性。
-
框架基石:支撑几乎所有主流Java框架的核心功能实现。
7.2 缺点
-
性能较低:相较于直接调用,反射存在额外解析与校验开销。
-
安全性降低:突破访问权限,破坏类的封装性,存在安全风险。
-
代码可读性差:反射代码繁琐、逻辑隐晦,增加维护成本。
-
编译期无法校验:类名、方法名、字段名错误仅在运行时报错,调试难度更高。
八、反射实战应用场景
反射不会用于普通业务代码开发,核心用于通用工具、框架底层、中间件开发,常见场景如下:
-
Spring IoC容器:通过反射动态创建Bean对象、注入属性,实现依赖注入。
-
ORM框架:MyBatis、Hibernate通过反射将数据库查询结果自动封装为实体对象。
-
单元测试:JUnit通过反射调用测试方法,无需手动创建对象调用。
-
动态代理:JDK动态代理底层依赖反射实现方法拦截与增强。
-
通用工具类:对象拷贝、属性对比、JSON解析实体封装等通用工具。
-
配置化加载:通过配置文件类路径,动态加载类并执行对应逻辑。
九、本章核心总结
-
反射是Java运行时动态解析、操作类结构的核心机制,赋予Java动态编程能力。
-
反射唯一核心入口是Class对象,一个类仅对应一个Class实例。
-
获取Class对象三种方式:
对象.getClass()、类名.class、Class.forName()。 -
反射核心操作:动态创建对象、操作构造器、读写字段、调用方法。
-
setAccessible(true)可突破权限限制,同时优化反射性能。 -
反射灵活但性能较差、封装性弱,适合框架底层与通用工具开发,不适合高频业务场景。
更多推荐
所有评论(0)