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对象是反射的"入口",有三种方式获取:
| 方式 | 语法 | 场景 |
|---|---|---|
| 类名.class | User.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💡小贴士:基本类型(
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 反射创建对象(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 倍,主要因为:
- JIT 编译器无法优化:反射调用时方法名是字符串,JVM 不知道会调哪个方法,无法做内联优化
- 每次调用都要查 Method 对象:需要权限检查、参数封装
- 参数 / 返回值要装箱拆箱:基本类型与包装类转换
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 提供的一种在运行时动态获取类信息并操作对象的能力。
它允许程序在运行时查看任意类的结构(如字段、方法、构造器 |Class、Constructor、Field、Method),并通过这些信息创建对象、调用方法、修改属性,即使在编译期不知道具体类型。
Q1:反射为什么慢?
反射调用比直接调用慢10-100 倍,主要因为:
- JIT 无法优化:反射调用时方法名是字符串,JVM 不知道会调哪个方法,无法做内联优化
- 每次都要权限检查:包括访问修饰符、参数类型检查
- 参数 / 返回值要装箱拆箱:基本类型与包装类转换
性能优化三板斧:缓存Method/Field对象 +setAccessible(true)关闭访问检查 + 避免热路径使用。
Q2:getXxx和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 / 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基础笔记
🎯如果觉得本文对你有帮助,欢迎点赞、收藏、关注!你的支持是我持续创作的最大动力 💪
