【java基础难点:“反射“】别再觉得它难了!一文让你彻底理解!
目录
1.问题初现
2.反射登场
反射的原理
反射在这个例子中干了什么?
3.反射应该怎么用
先准备一个测试类:Person.java
用反射操作它
4.反射到底用在哪些地方?
框架的依赖注入(Spring)
单元测试框架(JUnit)
动态代理(AOP)
通用 JSON 转换工具
5.反射的优缺点
优点
缺点
6.什么时候用反射
7.总结
前言:
反射这个词语想必大家都不陌生吧,在初学javase的时候这个点是很多人都无法理解的,这到底是什么玩意儿啊!!!,别慌今天我带大家彻底理解反射,不光从概念理解,更从实际代码出发,现在跟着我一起学习反射。
1.问题初现
你现在写了这样一段代码
// 编译时,你定义了两个动物类 class Dog { public void speak() { System.out.println("汪汪"); } } class Cat { public void speak() { System.out.println("喵喵"); } }然后你想当用户输入dog时,程序输出:”汪汪“,输入猫时,程序输出:”喵喵“,你是不是会想到直接用if----else来判断,那如果现在有100只动物呢,你会一个个去判断吗,很麻烦对吧,用循环吗,怎么循环?是不是不知道怎么办了,那么反射来拯救你。
2.反射登场
反射是 Java 提供的一种运行时自省机制,它允许程序在运行期间动态地获取类的完整信息(比如类名、方法、字段、构造器),并且可以操作这些成员——包括创建对象、调用方法、修改字段值,甚至访问私有成员。
它的核心是Class对象。每个类在加载到 JVM 后,都会对应一个Class对象,里面存着这个类的所有结构信息。反射就是通过这个Class对象来“窥探”和操作类的内部。
反射的原理
以上面的问题为例子,现在写一个反射代码
String animalType = 用户输入; // 运行时才能确定,比如 "Dog" // 反射动态加载类、创建对象、调用方法 Class<?> clazz = Class.forName(animalType); // 根据字符串加载类 Object animal = clazz.newInstance(); // 创建实例 ==new animalType() Method method = clazz.getMethod("speak"); // 获取 speak 方法 method.invoke(animal); // 调用方法==animal.speak()要彻底理解反射,必须知道 Java 虚拟机(JVM)的类加载机制:
当你编写一个
.java文件,编译后得到.class字节码文件。当程序运行到需要用到某个类时,JVM 的类加载器会将这个类的
.class文件读入内存,并为之创建一个Class对象。这个
Class对象就像该类的“元数据”代表,它包含了这个类的所有信息:类名、父类、实现的接口、构造器列表、方法列表、字段列表等。这个class对象就是反射的入口
反射在这个例子中干了什么?
动态加载类:用
Class.forName("Dog")把Dog这个类加载到内存,得到它的Class对象。如果没有反射,你只能用new Dog(),但那样类名就写死了。动态创建对象:
clazz.newInstance()相当于new Dog(),但这里事先不知道具体是哪个类。动态查找方法:
getMethod("speak")找到类里的speak方法。动态调用方法:
invoke(animal)实际执行animal.speak()。整个过程都是程序运行时根据用户的输入现场决定的,而不是编译时就写死。这就是反射的价值:让程序在运行时拥有“临时改变主意”的能力,变得非常灵活.
3.反射应该怎么用
反射的套路非常固定,记住这四步:
获取
Class对象通过
Class对象获取构造器、方法或字段设置可访问(如果是私有成员,需要
setAccessible(true))调用方法或修改字段
先准备一个测试类:Person.java
public class Person { private String name; public int age; public Person() {} private Person(String name) { this.name = name; } public Person(String name, int age) { this.name = name; this.age = age; } private void setName(String name) { this.name = name; } public String getName() { return name; } public void sayHello() { System.out.println("Hello, I'm " + name); } }用反射操作它
import java.lang.reflect.*; public class ReflectionDemo { public static void main(String[] args) throws Exception { // 1. 获取 Class 对象(三种常用方式) Class<?> clazz = Class.forName("Person"); // 方式1:类名字符串 // Class<?> clazz = Person.class; // 方式2:类名.class // Person p = new Person(); Class<?> clazz = p.getClass(); // 方式3:对象.getClass() // 2. 创建对象(通过公有构造器) Constructor<?> constructor = clazz.getConstructor(String.class, int.class); Object obj = constructor.newInstance("张三", 25); // 相当于 new Person("张三", 25) System.out.println(obj); // 输出 Person{name='张三', age=25} // 3. 操作私有字段 name Field nameField = clazz.getDeclaredField("name"); nameField.setAccessible(true); // 暴力反射:允许访问私有 nameField.set(obj, "李四"); // 修改私有字段的值 System.out.println("修改后 name = " + nameField.get(obj)); // 读取私有字段 // 4. 调用私有方法 setName Method setNameMethod = clazz.getDeclaredMethod("setName", String.class); setNameMethod.setAccessible(true); setNameMethod.invoke(obj, "王五"); // 相当于 obj.setName("王五") System.out.println("调用私有方法后 name = " + nameField.get(obj)); // 5. 调用公有方法 sayHello Method sayHelloMethod = clazz.getMethod("sayHello"); sayHelloMethod.invoke(obj); // 输出 Hello, I'm 王五 } }看到没?我们全程没有直接写new Person(),也没有直接调用obj.setName(),但通过反射,我们做到了完全相同的事情,而且类名、方法名、字段名都可以是字符串变量。
4.反射到底用在哪些地方?
框架的依赖注入(Spring)
Spring 的配置文件里写着<bean class="com.example.UserService"/>,它怎么知道这个类在哪里?就是通过反射加载的:
Class<?> clazz = Class.forName("com.example.UserService"); Object userService = clazz.newInstance();单元测试框架(JUnit)
JUnit 会扫描你写的测试类,找到所有加了@Test注解的方法,然后用反射调用它们:
for (Method method : testClass.getMethods()) { if (method.isAnnotationPresent(Test.class)) { method.invoke(testInstance); } }动态代理(AOP)
Spring AOP、MyBatis 的 Mapper 动态代理,都是通过反射 + 动态代理生成的。
通用 JSON 转换工具
String json = "{"; for (Field field : obj.getClass().getDeclaredFields()) { field.setAccessible(true); String fieldName = field.getName(); Object value = field.get(obj); json += "\"" + fieldName + "\":\"" + value + "\","; } json = json.substring(0, json.length() - 1); // 去掉最后一个逗号 json += "}"; {"name":"张三","age":25}
obj.getClass().getDeclaredFields()
obj是某个对象,比如一个Person实例。
obj.getClass()获取这个对象的Class对象(就是前面说的“类的身份证”)。
getDeclaredFields()返回这个类中所有声明的字段,不管它是public还是private。返回的是一个Field数组,数组里每个元素代表一个字段。
for (Field field : 字段数组)这就是一个增强 for 循环,依次处理每一个字段。
field.setAccessible(true);
默认情况下,反射不能访问私有字段(比如
private String name)。
setAccessible(true)就是“暴力打开”访问权限,告诉 Java:我知道这个字段可能是私有的,但我就是要访问它,你放行。这样后面才能读取或修改这个字段的值。
String fieldName = field.getName();获取字段的名字,比如字段名叫
"name"或"age"。
Object value = field.get(obj);获取这个字段在当前对象
obj中的实际值。
如果字段是
name,obj的name是"张三",那么value就是字符串"张三"。如果字段是
age,值是25,那么value就是整数25(注意此时value类型是Object,但实际值是Integer)。
5.反射的优缺点
优点
灵活性高:可以动态加载类、调用方法,代码高度抽象。
扩展性强:支持插件化架构,无需修改核心代码就能扩展功能。
缺点
性能稍慢:反射比直接调用慢几十倍(因为有类型检查、权限检查等),在循环中慎用。
可能破坏封装:
setAccessible(true)可以调用私有方法,如果滥用会破坏类的封装性,带来安全风险。代码复杂:反射代码往往比直接调用冗长,且需要处理一大堆受检异常(
ClassNotFoundException、NoSuchMethodException、InvocationTargetException等)。
6.什么时候用反射
你明确知道要操作哪个类,比如new Person() | 不用反射,直接写 |
| 类名、方法名来自配置文件、用户输入 | 用反射,动态处理 |
| 你在写一个通用框架或工具(如 JSON 转换、ORM) | 用反射,处理未知类型 |
| 你想在单元测试中调用私有方法 | 用反射(配合setAccessible) |
| 核心业务逻辑,对性能要求极高 | 尽量不用反射,或使用缓存、MethodHandle优化 |
反射是一把双刃剑,它让 Java 从静态语言变得“动态”起来,但也要在正确的地方使用它。希望这篇文章能帮你揭开反射的神秘面纱,下次看到反射代码时,能轻松看懂它的意图。
7.总结
反射并不神秘,它只是一种手段,能让程序员操作未知的事物,让程序变得非常灵活,如果这篇文章对你有帮助请给一个赞或者关注,创作不易感谢你的阅读。
