【java基础难点:“反射“】别再觉得它难了!一文让你彻底理解!
目录
前言:
反射这个词语想必大家都不陌生吧,在初学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.总结
反射并不神秘,它只是一种手段,能让程序员操作未知的事物,让程序变得非常灵活,如果这篇文章对你有帮助请给一个赞或者关注,创作不易感谢你的阅读。

更多推荐



所有评论(0)