当前位置: 首页 > news >正文

Java 反射:从入门到精通,一篇打通你的任督二脉

Java 反射:从入门到精通,一篇打通你的任督二脉

📌文章导读:反射(Reflection)是 Java 进阶的核心技能,也是面试高频考点。本文从"反射是什么"出发,层层递进讲透反射的底层原理、核心 API、典型应用、性能优化与面试高频问题。文末附上一张完整的反射知识地图,建议收藏!


一、写在前面:为什么学反射?

很多 Java 学习者初学反射时,都会冒出这么几个问题:

  • 写业务代码用不到反射啊?为啥要学?
  • 反射代码又臭又长,性能还差,为啥大厂面试必问?
  • Spring、MyBatis、JUnit 这些框架到底是怎么"看穿"我写的类的?

这些问题背后,其实指向的是同一个答案:

反射是 Java 框架的基石。你不学反射,永远只能"用框架";学完反射,你才能"理解框架"。

例如 Spring 的依赖注入(DI):

@ServicepublicclassUserService{@AutowiredprivateUserMapperuserMapper;// 谁帮我赋值的?}

userMapper字段是private的,Spring 是怎么把实现类塞进去的?答案是:反射 + 暴力访问private

掌握反射,你就拥有了"看穿 Java 程序运行内幕"的能力。


二、反射是什么?

2.1 一句话理解

反射是 Java 在"运行期"动态获取类信息、创建对象、调用方法、读写字段的能力。

关键词是"运行期"。

正常情况下,我们写代码是"编译期"就知道要操作的类的:

// 编译期就知道 UserService 是什么UserServiceservice=newUserService();service.save(user);

而反射允许我们在"运行期"才知道要操作的类是什么:

// 运行期才知道类名是 "com.xxx.UserService"Class<?>clazz=Class.forName("com.xxx.UserService");Objectinstance=clazz.getDeclaredConstructor().newInstance();MethodsaveMethod=clazz.getMethod("save",User.class);saveMethod.invoke(instance,user);

2.2 反射发生在 JVM 的哪一层?

先看一张经典的 JVM 体系图:

反射 API 位于java.lang.reflect包下,由 JDK 提供。它的能力是JVM 在加载类(ClassLoader)之后赋予我们的——JVM 在把.class文件加载进内存后,会生成一个Class对象来描述这个类的所有信息(字段、方法、构造器、注解等),反射就是对这个Class对象的操作。

所以,反射执行的时机,是在 Java 程序执行流程的"运行期":

反射 =运行期操作 Class 对象


三、反射三件套:Class / Constructor / Method / Field

3.1 Class 对象的三种获取方式

Class对象是反射的"入口",有三种方式获取:

方式语法场景
类名.classUser.class编译期已知,性能最好
实例.getClass()user.getClass()已有对象实例
Class.forName()Class.forName("com.xxx.User")类名是字符串(最常用)
// 方式一:类名.classClass<User>c1=User.class;// 方式二:getClass()Useruser=newUser();Class<?extendsUser>c2=user.getClass();// 方式三:Class.forName() —— 框架中最常用Class<?>c3=Class.forName("com.xxx.User");// 验证:三种方式获取的是同一个 Class 对象System.out.println(c1==c2);// trueSystem.out.println(c1==c3);// true

💡小贴士:基本类型(intlong等)也有 Class 对象,但基本类型的 Class 与包装类不同int.class != Integer.class)。

3.2 反射"四件套"关系图

java.lang.Class │ ┌───────────────┼───────────────┐ ▼ ▼ ▼ Constructor Method Field (构造器) (方法) (字段) │ │ │ ▼ ▼ ▼ newInstance() invoke(obj, args) get/set(obj, value)

3.3 反射创建对象(Constructor)

publicclassUser{privateStringname;publicUser(){}publicUser(Stringname){this.name=name;}}// ====== 反射创建对象 ======Class<User>clazz=User.class;// 调用无参构造Useru1=clazz.getDeclaredConstructor().newInstance();// 调用有参构造Useru2=clazz.getDeclaredConstructor(String.class).newInstance("Alice");// ⚠️ Class.newInstance() 已废弃(Java 9+)// 推荐使用 clazz.getDeclaredConstructor().newInstance()

3.4 反射调用方法(Method)

MethodsetName=User.class.getMethod("setName",String.class);setName.invoke(u1,"Bob");// 等价于 u1.setName("Bob")MethodgetName=User.class.getMethod("getName");Stringname=(String)getName.invoke(u1);

3.5 反射读写字段(Field)

FieldnameField=User.class.getDeclaredField("name");nameField.setAccessible(true);// ⚠️ 突破 private 限制// 读Stringname=(String)nameField.get(u2);// 写nameField.set(u1,"Bob");

🚨重要setAccessible(true)是反射"破封装"的开关,必须显式调用才能访问private成员。


四、踩坑重灾区:getXxxvsgetDeclaredXxx

这是反射中最容易混淆的 API:

方法范围是否包含继承
getMethod(String, ...)public方法✅ 包含父类
getDeclaredMethod(String, ...)所有方法(含 private)❌ 仅本类
getField(String)public字段✅ 包含父类
getDeclaredField(String)所有字段❌ 仅本类
getConstructors()public构造器❌ 仅本类
getDeclaredConstructors()所有构造器❌ 仅本类

记忆口诀

  • Declared的:全都要,仅本类
  • 不带Declared的:仅 public,包含父类
// 经典踩坑:classParent{publicvoidpublicMethod(){}privatevoidprivateMethod(){}}classChildextendsParent{publicvoidchildMethod(){}}Class<Child>clazz=Child.class;clazz.getMethod("publicMethod");// ✅ 父类的 public 方法能找到clazz.getMethod("privateMethod");// ❌ 抛 NoSuchMethodExceptionclazz.getDeclaredMethod("privateMethod");// ✅ 但 private 找不到父类的clazz.getDeclaredMethod("childMethod");// ✅ 自己的方法

五、反射的"杀手级"应用场景

5.1 Spring IoC / DI

Spring 启动时扫描@Component注解,反射创建 Bean

// Spring 内部伪代码for(Class<?>clazz:scanAllClasses()){if(clazz.isAnnotationPresent(Component.class)){Objectbean=clazz.getDeclaredConstructor().newInstance();beanMap.put(clazz.getSimpleName(),bean);}}

依赖注入时,反射读写字段

// Spring 内部伪代码for(Fieldfield:bean.getClass().getDeclaredFields()){if(field.isAnnotationPresent(Autowired.class)){field.setAccessible(true);Objectdependency=beanMap.get(field.getType().getSimpleName());field.set(bean,dependency);// 注入依赖}}

5.2 MyBatis 结果集映射

// MyBatis 内部伪代码List<User>users=newArrayList<>();while(resultSet.next()){Useruser=User.class.getDeclaredConstructor().newInstance();for(Fieldfield:User.class.getDeclaredFields()){StringcolumnName=camelToUnderline(field.getName());Objectvalue=resultSet.getObject(columnName);field.setAccessible(true);field.set(user,value);}users.add(user);}

5.3 Jackson / Gson 序列化

// Jackson 内部伪代码publicStringtoJson(Objectobj){StringBuilderjson=newStringBuilder("{");for(Fieldfield:obj.getClass().getDeclaredFields()){field.setAccessible(true);json.append("\"").append(field.getName()).append("\":").append("\"").append(field.get(obj)).append("\",");}returnjson.append("}").toString();}

5.4 JDBC 驱动加载

// 经典反射加载 MySQL 驱动Class.forName("com.mysql.cj.jdbc.Driver");// 现在的 SPI 机制(ServiceLoader)也基于反射

5.5 JUnit 测试框架

// JUnit 内部伪代码for(Methodmethod:testClass.getDeclaredMethods()){if(method.isAnnotationPresent(Test.class)){Objectinstance=testClass.getDeclaredConstructor().newInstance();method.setAccessible(true);method.invoke(instance);// 调用 @Test 方法}}

💡总结框架 = 注解 + 反射 + 动态代理。反射让框架"看见"你写的类,框架再帮你注入依赖、调用方法。


六、反射的代价与性能优化

6.1 反射为什么慢?

反射调用比直接调用慢10-100 倍,主要因为:

  1. JIT 编译器无法优化:反射调用时方法名是字符串,JVM 不知道会调哪个方法,无法做内联优化
  2. 每次调用都要查 Method 对象:需要权限检查、参数封装
  3. 参数 / 返回值要装箱拆箱:基本类型与包装类转换

6.2 性能优化三板斧

① 缓存反射对象
// ❌ 每次都查 Method(慢)publicObjectinvoke(Objecttarget,StringmethodName,Object...args)throwsException{Methodmethod=target.getClass().getMethod(methodName,...);returnmethod.invoke(target,args);}// ✅ 缓存 Method(快 50 倍)privatestaticfinalMap<Class<?>,Map<String,Method>>METHOD_CACHE=newConcurrentHashMap<>();publicObjectinvoke(Objecttarget,StringmethodName,Object...args)throwsException{Methodmethod=METHOD_CACHE.computeIfAbsent(target.getClass(),k->newConcurrentHashMap<>()).computeIfAbsent(methodName,k->{try{returnk.getMethod(k.getSimpleName(),/*...*/);}catch(Exceptione){thrownewRuntimeException(e);}});returnmethod.invoke(target,args);}
setAccessible(true)关闭检查
FieldnameField=User.class.getDeclaredField("name");nameField.setAccessible(true);// 关闭 Java 语言访问检查,性能提升 4 倍+
③ 避免热路径使用反射

业务核心循环里不要用反射,一次性初始化或低频场景使用最佳。

6.3 Effective Java 的建议

📖Item 65: Prefer interfaces to reflection

反射是"逃生口",不是日常工具。它有以下代价:

  • 编译期类型检查失效→ 运行时才报错
  • 代码笨拙冗长→ 可读性差
  • 性能损耗→ 不适合高频调用

除非万不得已(如框架开发、动态加载类),否则不要在业务代码中使用反射。


七、面试高频问题

Q0:反射是什么?

反射(Reflection)是 Java 提供的一种在运行时动态获取类信息并操作对象的能力。

它允许程序在运行时查看任意类的结构(如字段、方法、构造器 |ClassConstructorFieldMethod),并通过这些信息创建对象、调用方法、修改属性,即使在编译期不知道具体类型。

Q1:反射为什么慢?

反射调用比直接调用慢10-100 倍,主要因为:

  1. JIT 无法优化:反射调用时方法名是字符串,JVM 不知道会调哪个方法,无法做内联优化
  2. 每次都要权限检查:包括访问修饰符、参数类型检查
  3. 参数 / 返回值要装箱拆箱:基本类型与包装类转换

性能优化三板斧:缓存Method/Field对象 +setAccessible(true)关闭访问检查 + 避免热路径使用。

Q2:getXxxgetDeclaredXxx的区别?

见第 4 节。带Declared的:所有访问修饰符 + 仅本类;不带Declared的:仅 public + 包含父类。

Q3:反射能获取private字段的值吗?怎么操作?

能。使用getDeclaredField()获取字段,然后setAccessible(true)突破访问限制。

Q4:泛型 + 反射有什么坑?

类型擦除后,反射拿到的是Object/Class<?>,无法直接获取泛型参数的实际类型(除非用Type体系 +ParameterizedType)。

Q5:注解 + 反射 + 动态代理的关系?

┌────────────────────────────────────────────────┐ │ 框架底层的"三件套" │ ├────────────────────────────────────────────────┤ │ 注解:标记行为(@Transactional / @Autowired) │ │ ↓ │ │ 反射:读取注解、获取类信息(运行时) │ │ ↓ │ │ 动态代理:增强方法(事务 / 日志 / 权限) │ └────────────────────────────────────────────────┘

Spring AOP = 注解 + 反射 + 动态代理


八、反射知识地图

Java 反射 ├── 1. Class 对象 │ ├── 类名.class │ ├── getClass() │ └── Class.forName() ← 最常用 │ ├── 2. 四件套 │ ├── Class(类信息) │ ├── Constructor(构造器) │ ├── Method(方法) │ └── Field(字段) │ ├── 3. 核心 API 速记 │ ├── getXxx → public + 包含父类 │ └── getDeclaredXxx → 所有 + 仅本类 │ ├── 4. 应用场景 │ ├── Spring IoC / DI(@Component + @Autowired) │ ├── MyBatis(结果集映射) │ ├── Jackson(JSON 序列化) │ ├── JDBC(Driver 加载) │ └── JUnit(@Test 执行) │ ├── 5. 性能优化 │ ├── 缓存 Method/Field │ ├── setAccessible(true) │ └── 避免热路径 │ └── 6. 面试高频 ├── 为什么慢? ├── getXxx vs getDeclaredXxx ├── 反射 + 泛型 └── 反射 + 注解 + 动态代理的关系

九、写在最后

反射是 Java 进阶的"分水岭"。学懂反射,你才真正开始理解 Java 生态——Spring、MyBatis、JPA、Hibernate 等等,它们的底层都离不开反射。

但也要记住:

反射是一把双刃剑。它赋予你强大的能力,也带来性能、封装、可读性的代价。

正确使用反射的姿势是:理解原理 → 框架里用得明白 → 业务代码里谨慎使用


参考资料

  • Oracle: The Reflection API
  • Java 25 Class API
  • Effective Java, 3rd Edition, Item 65: Prefer interfaces to reflection
  • Spring Framework Reference: IoC Container
  • Baeldung: Java Reflection
  • java基础笔记

🎯如果觉得本文对你有帮助,欢迎点赞、收藏、关注!你的支持是我持续创作的最大动力 💪

http://www.jsqmd.com/news/989008/

相关文章:

  • 个人散单寄快递怎么省钱?2026低价渠道实测对比 - 快递物流资讯
  • 国产大模型×魔珐星云:让AI从“能思考“到“能表达“的具身智能实践
  • 数据的加密与解密(00:50)
  • 数据的加密与解密(00:55)
  • AI时代的真本事:用更少的Token做更好的事
  • 探析2026年济南柴机油市场:为何山东嘉荷润滑油成为可靠之选? - 品牌鉴赏官2026
  • 上海婚纱摄影哪家好?签约前把口头承诺逐条对进合同 - eee888
  • MC9S12XE微控制器GPIO深度解析:从端口集成模块到驱动设计实战
  • 2026年工业扫地机推荐榜单:电动扫地机/扫地机/厂房扫地机/仓储扫地机品牌实力与高效清洁深度解析 - 品牌发掘
  • 2026年近期北二环驾校专业选择与行业趋势深度洞察 - 品牌鉴赏官2026
  • 2026年 五菱观光车实力厂家推荐榜单:景区代步/巡逻接待/绿色出行热销车型精选与口碑解析 - 品牌发掘
  • 2 行指令,搞定所有 AI 项目文档
  • 2026年新发布:江苏地区优质托辊钢管供应厂家综合寻源指南 - 品牌鉴赏官2026
  • 深入解析PWM对齐模式与H桥配置:提升电机驱动效率与EMC性能
  • 上海婚纱摄影哪家好?AI能查到,但客片只能自己到店看 - eee888
  • 专业隔音门厂家技术解析:选材到验收的核心标准 - 优质品牌商家
  • 2026年草坪技术分享 附成都擎枫园艺服务参考 - 优质品牌商家
  • 2026噪声治理技术分享:四川艾声环保对接推荐 - 优质品牌商家
  • 2026年成都酒吧装修品牌盘点:成都店铺翻新/成都店面设计装修/成都旧房翻新/三家本土实操型机构解析 - 优质品牌商家
  • 2026年东港熔模铸造工厂口碑推荐榜单:精密铸件/不锈钢熔模/硅溶胶工艺源头厂家实力解析 - 品牌发掘
  • 2026年当前鄂州买新房中介选择全攻略:专业、诚信与价值的深度解析 - 品牌鉴赏官2026
  • 2026年四川霖澳律师事务所联系服务全维度评测 - 优质品牌商家
  • 洞察2026:浙江地区备受赞誉的农用喷枪批发合作伙伴优选指南 - 品牌鉴赏官2026
  • 2026年国内专利数据服务商联系渠道及服务能力评测 - 优质品牌商家
  • 数据的加密与解密(00:52)
  • 数据的加密与解密(00:56)
  • 商用四川火锅底料厂家实测评测:靠谱供应商筛选推荐 - 优质品牌商家
  • 2026上海奉贤区龙洋银元收购 一个电话快速上门 - 沪上贵金属口碑推荐官
  • 2026年 手工净化板供应商推荐榜单:洁净车间首选,高密封防尘与美观兼具的专业品牌解析 - 品牌发掘
  • 广州番禺黄金奢侈品回收哪家强?金小福24小时上门服务更贴心 - 花生花生1