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

发布时间:2026/6/11 1:04:36
Java 反射:从入门到精通,一篇打通你的任督二脉 Java 反射从入门到精通一篇打通你的任督二脉文章导读反射Reflection是 Java 进阶的核心技能也是面试高频考点。本文从反射是什么出发层层递进讲透反射的底层原理、核心 API、典型应用、性能优化与面试高频问题。文末附上一张完整的反射知识地图建议收藏一、写在前面为什么学反射很多 Java 学习者初学反射时都会冒出这么几个问题写业务代码用不到反射啊为啥要学反射代码又臭又长性能还差为啥大厂面试必问Spring、MyBatis、JUnit 这些框架到底是怎么看穿我写的类的这些问题背后其实指向的是同一个答案反射是 Java 框架的基石。你不学反射永远只能用框架学完反射你才能理解框架。例如 Spring 的依赖注入DIServicepublicclassUserService{AutowiredprivateUserMapperuserMapper;// 谁帮我赋值的}userMapper字段是private的Spring 是怎么把实现类塞进去的答案是反射 暴力访问private。掌握反射你就拥有了看穿 Java 程序运行内幕的能力。二、反射是什么2.1 一句话理解反射是 Java 在运行期动态获取类信息、创建对象、调用方法、读写字段的能力。关键词是运行期。正常情况下我们写代码是编译期就知道要操作的类的// 编译期就知道 UserService 是什么UserServiceservicenewUserService();service.save(user);而反射允许我们在运行期才知道要操作的类是什么// 运行期才知道类名是 com.xxx.UserServiceClass?clazzClass.forName(com.xxx.UserService);Objectinstanceclazz.getDeclaredConstructor().newInstance();MethodsaveMethodclazz.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 / Field3.1 Class 对象的三种获取方式Class对象是反射的入口有三种方式获取方式语法场景类名.classUser.class编译期已知性能最好实例.getClass()user.getClass()已有对象实例Class.forName()Class.forName(com.xxx.User)类名是字符串最常用// 方式一类名.classClassUserc1User.class;// 方式二getClass()UserusernewUser();Class?extendsUserc2user.getClass();// 方式三Class.forName() —— 框架中最常用Class?c3Class.forName(com.xxx.User);// 验证三种方式获取的是同一个 Class 对象System.out.println(c1c2);// trueSystem.out.println(c1c3);// true小贴士基本类型int、long等也有 Class 对象但基本类型的 Class 与包装类不同int.class ! Integer.class。3.2 反射四件套关系图java.lang.Class │ ┌───────────────┼───────────────┐ ▼ ▼ ▼ Constructor Method Field (构造器) (方法) (字段) │ │ │ ▼ ▼ ▼ newInstance() invoke(obj, args) get/set(obj, value)3.3 反射创建对象ConstructorpublicclassUser{privateStringname;publicUser(){}publicUser(Stringname){this.namename;}}// 反射创建对象 ClassUserclazzUser.class;// 调用无参构造Useru1clazz.getDeclaredConstructor().newInstance();// 调用有参构造Useru2clazz.getDeclaredConstructor(String.class).newInstance(Alice);// ⚠️ Class.newInstance() 已废弃Java 9// 推荐使用 clazz.getDeclaredConstructor().newInstance()3.4 反射调用方法MethodMethodsetNameUser.class.getMethod(setName,String.class);setName.invoke(u1,Bob);// 等价于 u1.setName(Bob)MethodgetNameUser.class.getMethod(getName);Stringname(String)getName.invoke(u1);3.5 反射读写字段FieldFieldnameFieldUser.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(){}}ClassChildclazzChild.class;clazz.getMethod(publicMethod);// ✅ 父类的 public 方法能找到clazz.getMethod(privateMethod);// ❌ 抛 NoSuchMethodExceptionclazz.getDeclaredMethod(privateMethod);// ✅ 但 private 找不到父类的clazz.getDeclaredMethod(childMethod);// ✅ 自己的方法五、反射的杀手级应用场景5.1 Spring IoC / DISpring 启动时扫描Component注解反射创建 Bean// Spring 内部伪代码for(Class?clazz:scanAllClasses()){if(clazz.isAnnotationPresent(Component.class)){Objectbeanclazz.getDeclaredConstructor().newInstance();beanMap.put(clazz.getSimpleName(),bean);}}依赖注入时反射读写字段// Spring 内部伪代码for(Fieldfield:bean.getClass().getDeclaredFields()){if(field.isAnnotationPresent(Autowired.class)){field.setAccessible(true);ObjectdependencybeanMap.get(field.getType().getSimpleName());field.set(bean,dependency);// 注入依赖}}5.2 MyBatis 结果集映射// MyBatis 内部伪代码ListUserusersnewArrayList();while(resultSet.next()){UseruserUser.class.getDeclaredConstructor().newInstance();for(Fieldfield:User.class.getDeclaredFields()){StringcolumnNamecamelToUnderline(field.getName());ObjectvalueresultSet.getObject(columnName);field.setAccessible(true);field.set(user,value);}users.add(user);}5.3 Jackson / Gson 序列化// Jackson 内部伪代码publicStringtoJson(Objectobj){StringBuilderjsonnewStringBuilder({);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)){ObjectinstancetestClass.getDeclaredConstructor().newInstance();method.setAccessible(true);method.invoke(instance);// 调用 Test 方法}}总结框架 注解 反射 动态代理。反射让框架看见你写的类框架再帮你注入依赖、调用方法。六、反射的代价与性能优化6.1 反射为什么慢反射调用比直接调用慢10-100 倍主要因为JIT 编译器无法优化反射调用时方法名是字符串JVM 不知道会调哪个方法无法做内联优化每次调用都要查 Method 对象需要权限检查、参数封装参数 / 返回值要装箱拆箱基本类型与包装类转换6.2 性能优化三板斧① 缓存反射对象// ❌ 每次都查 Method慢publicObjectinvoke(Objecttarget,StringmethodName,Object...args)throwsException{Methodmethodtarget.getClass().getMethod(methodName,...);returnmethod.invoke(target,args);}// ✅ 缓存 Method快 50 倍privatestaticfinalMapClass?,MapString,MethodMETHOD_CACHEnewConcurrentHashMap();publicObjectinvoke(Objecttarget,StringmethodName,Object...args)throwsException{MethodmethodMETHOD_CACHE.computeIfAbsent(target.getClass(),k-newConcurrentHashMap()).computeIfAbsent(methodName,k-{try{returnk.getMethod(k.getSimpleName(),/*...*/);}catch(Exceptione){thrownewRuntimeException(e);}});returnmethod.invoke(target,args);}②setAccessible(true)关闭检查FieldnameFieldUser.class.getDeclaredField(name);nameField.setAccessible(true);// 关闭 Java 语言访问检查性能提升 4 倍③ 避免热路径使用反射业务核心循环里不要用反射一次性初始化或低频场景使用最佳。6.3 Effective Java 的建议Item 65: Prefer interfaces to reflection反射是逃生口不是日常工具。它有以下代价编译期类型检查失效→ 运行时才报错代码笨拙冗长→ 可读性差性能损耗→ 不适合高频调用除非万不得已如框架开发、动态加载类否则不要在业务代码中使用反射。七、面试高频问题Q0反射是什么反射Reflection是 Java 提供的一种在运行时动态获取类信息并操作对象的能力。它允许程序在运行时查看任意类的结构如字段、方法、构造器 |Class、Constructor、Field、Method并通过这些信息创建对象、调用方法、修改属性即使在编译期不知道具体类型。Q1反射为什么慢反射调用比直接调用慢10-100 倍主要因为JIT 无法优化反射调用时方法名是字符串JVM 不知道会调哪个方法无法做内联优化每次都要权限检查包括访问修饰符、参数类型检查参数 / 返回值要装箱拆箱基本类型与包装类转换性能优化三板斧缓存Method/Field对象 setAccessible(true)关闭访问检查 避免热路径使用。Q2getXxx和getDeclaredXxx的区别见第 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 / DIComponent Autowired │ ├── MyBatis结果集映射 │ ├── JacksonJSON 序列化 │ ├── JDBCDriver 加载 │ └── JUnitTest 执行 │ ├── 5. 性能优化 │ ├── 缓存 Method/Field │ ├── setAccessible(true) │ └── 避免热路径 │ └── 6. 面试高频 ├── 为什么慢 ├── getXxx vs getDeclaredXxx ├── 反射 泛型 └── 反射 注解 动态代理的关系九、写在最后反射是 Java 进阶的分水岭。学懂反射你才真正开始理解 Java 生态——Spring、MyBatis、JPA、Hibernate 等等它们的底层都离不开反射。但也要记住反射是一把双刃剑。它赋予你强大的能力也带来性能、封装、可读性的代价。正确使用反射的姿势是理解原理 → 框架里用得明白 → 业务代码里谨慎使用。参考资料Oracle: The Reflection APIJava 25 Class APIEffective Java, 3rd Edition, Item 65: Prefer interfaces to reflectionSpring Framework Reference: IoC ContainerBaeldung: Java Reflectionjava基础笔记如果觉得本文对你有帮助欢迎点赞、收藏、关注你的支持是我持续创作的最大动力