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 通用测试实体类
publicclassUser{// 私有成员变量privateStringusername;privateIntegerage;// 公共成员变量publicStringphone;// 无参构造方法publicUser(){}// 公共有参构造publicUser(Stringusername,Integerage){this.username=username;this.age=age;}// 私有有参构造privateUser(Stringphone){this.phone=phone;}// 普通公共方法publicvoidsayHello(){System.out.println("用户名:"+username+",年龄:"+age);}// 私有方法privatevoidupdateInfo(StringnewName){this.username=newName;System.out.println("用户名已更新为:"+newName);}// getter & setterpublicStringgetUsername(){returnusername;}publicvoidsetUsername(Stringusername){this.username=username;}publicIntegergetAge(){returnage;}publicvoidsetAge(Integerage){this.age=age;}}3.2 三种获取Class对象的方式
方式一:实例对象.getClass() (运行时获取)
通过类的实例对象调用原生方法获取Class对象,必须先创建对象,适用于已有实例对象的场景。
publicclassGetClassByInstance{publicstaticvoidmain(String[]args){// 创建实例对象Useruser=newUser();// 调用getClass()获取Class对象Class<?extendsUser>clazz=user.getClass();// 打印类全限定名System.out.println("类全限定名:"+clazz.getName());}}方式二:类名.class (编译期锁定,高效)
通过静态语法类名.class获取,无需创建对象,编译期即可识别,性能最优,适用于明确知道目标类的场景。
publicclassGetClassByStatic{publicstaticvoidmain(String[]args){// 静态语法获取Class对象Class<User>clazz=User.class;System.out.println("类名:"+clazz.getSimpleName());System.out.println("全限定类名:"+clazz.getName());}}方式三:Class.forName() (动态加载,最常用)
通过Class类静态方法传入类全限定名动态加载类,无需提前知晓类,支持配置化加载,是框架开发中最常用的方式,需捕获编译异常。
publicclassGetClassByForName{publicstaticvoidmain(String[]args)throwsClassNotFoundException{// 传入类的全限定包名+类名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(参数类型…) | 获取指定参数列表的任意权限构造方法 |
实操代码:获取构造器并动态创建对象
importjava.lang.reflect.Constructor;publicclassReflectConstructorDemo{publicstaticvoidmain(String[]args)throwsException{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);Useruser1=publicConstructor.newInstance("张三",22);user1.sayHello();// 4.通过私有构造创建对象(需开启权限放行)Constructor<User>privateConstructor=clazz.getDeclaredConstructor(String.class);privateConstructor.setAccessible(true);// 关闭权限校验Useruser2=privateConstructor.newInstance("13800138000");System.out.println("私有构造创建对象手机号:"+user2.phone);}}4.2 反射操作成员变量
常用API说明
| 方法名 | 功能描述 |
|---|---|
| getFields() | 获取所有公共成员变量数组 |
| getDeclaredFields() | 获取当前类所有权限成员变量数组 |
| getField(字段名) | 获取指定名称的公共成员变量 |
| getDeclaredField(字段名) | 获取指定名称的任意权限成员变量 |
实操代码:获取、修改私有/公共字段值
importjava.lang.reflect.Field;publicclassReflectFieldDemo{publicstaticvoidmain(String[]args)throwsException{Class<User>clazz=User.class;Useruser=clazz.newInstance();// 1.操作公共字段FieldphoneField=clazz.getField("phone");phoneField.set(user,"13900139000");System.out.println("公共手机号:"+phoneField.get(user));// 2.操作私有字段FieldnameField=clazz.getDeclaredField("username");nameField.setAccessible(true);// 放行私有权限nameField.set(user,"李四");System.out.println("私有用户名:"+nameField.get(user));}}4.3 反射操作成员方法
常用API说明
| 方法名 | 功能描述 |
|---|---|
| getMethods() | 获取所有公共方法(含父类) |
| getDeclaredMethods() | 获取当前类所有权限方法 |
| getMethod(方法名,参数类型…) | 获取指定公共方法 |
| getDeclaredMethod(方法名,参数类型…) | 获取指定任意权限方法 |
实操代码:调用公共、私有方法
importjava.lang.reflect.Method;publicclassReflectMethodDemo{publicstaticvoidmain(String[]args)throwsException{Class<User>clazz=User.class;Useruser=clazz.getDeclaredConstructor().newInstance();// 1.调用公共无参方法MethodhelloMethod=clazz.getMethod("sayHello");user.setUsername("王五");user.setAge(25);helloMethod.invoke(user);// 2.调用私有有参方法MethodupdateMethod=clazz.getDeclaredMethod("updateInfo",String.class);updateMethod.setAccessible(true);updateMethod.invoke(user,"王五更新");}}4.4 获取类的拓展信息
通过Class对象还可以获取类的包名、父类、实现接口、类名等基础拓展信息,常用于通用工具类开发:
publicclassReflectClassInfoDemo{publicstaticvoidmain(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万次执行赋值方法,对比普通调用与反射调用的耗时差异:
importjava.lang.reflect.Method;publicclassReflectPerformanceTest{publicstaticvoidmain(String[]args)throwsException{Useruser=newUser();intcount=100000;// 普通方法调用耗时longstart1=System.currentTimeMillis();for(inti=0;i<count;i++){user.setAge(20);}longend1=System.currentTimeMillis();System.out.println("普通调用耗时:"+(end1-start1)+"ms");// 反射方法调用耗时Class<User>clazz=User.class;MethodsetAgeMethod=clazz.getMethod("setAge",Integer.class);longstart2=System.currentTimeMillis();for(inti=0;i<count;i++){setAgeMethod.invoke(user,20);}longend2=System.currentTimeMillis();System.out.println("反射调用耗时:"+(end2-start2)+"ms");// 优化后反射耗时(关闭权限校验)setAgeMethod.setAccessible(true);longstart3=System.currentTimeMillis();for(inti=0;i<count;i++){setAgeMethod.invoke(user,20);}longend3=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)可突破权限限制,同时优化反射性能。反射灵活但性能较差、封装性弱,适合框架底层与通用工具开发,不适合高频业务场景。