Java反射机制(一):深入理解Class对象——从三种获取方式看JVM的类加载原理
目录
一、引言:为什么我们需要反射?
在常规 Java 编程中,我们写的代码在编译期就要确定类型:new Student()、student.study()。一切都必须是明确已知的。但如果我告诉你:类的名字在编译时根本不存在,而是写在配置文件里,程序运行时才能读到,比如:
payService=com.mybank.WeChatPayService
你该怎么办?用 new 是做不到的,因为你不知道类名。这时候,Java 的反射机制就出场了——它允许程序在运行时动态获取一个类的完整信息(构造器、方法、字段),并动态调用它们。
反射是框架(Spring、MyBatis、Hibernate)的底层基石。理解了反射,你就拿到了看懂框架源码的第一把钥匙。
二、实验目标与准备
本次实验只做一件事:用三种不同的方式拿到一个类的 Class 对象,并验证它们其实是同一个对象。
环境准备
-
JDK 17
-
VS Code + Java 扩展包
-
项目结构:
java_reflection_blog/ └── src/ ├── com/reflection/demo/Student.java └── Experiment1.java
【截图1:VS Code 中的项目包结构】
三、编写目标类:Student
我们先写一个普通的 Java 类,它包含:
-
私有字段
name、age -
无参构造、有参构造
-
公有方法
study() -
私有方法
secret()(为了后面演示暴力反射)
package com.reflection.demo; public class Student { private String name; private int age; public Student() { this.name = "默认学生"; this.age = 18; } public Student(String name, int age) { this.name = name; this.age = age; } public void study() { System.out.println(name + " 正在学习,年龄:" + age); } private void secret() { System.out.println("这是一个私有方法,只能被反射调用"); } @Override public String toString() { return "Student{name='" + name + "', age=" + age + "}"; } }
【截图2:Student.java 完整代码】
这里故意写了一个私有方法 secret(),目的是为后续实验展示 setAccessible(true) 的威力。但目前第一步只关注 Class 对象,所以暂时不用它。
四、实验核心:三种方式获取 Class 对象
新建 Experiment1.java:
import com.reflection.demo.Student;
public class Experiment1 {
public static void main(String[] args) throws ClassNotFoundException {
// 方式一:Class.forName() —— 最动态,字符串驱动
Class<?> clazz1 = Class.forName("com.reflection.demo.Student");
System.out.println("方式1:" + clazz1.getName());
// 方式二:类名.class —— 编译时已知,最简洁
Class<Student> clazz2 = Student.class;
System.out.println("方式2:" + clazz2.getName());
// 方式三:对象.getClass() —— 运行时获取实际类型
Student stu = new Student();
Class<?> clazz3 = stu.getClass();
System.out.println("方式3:" + clazz3.getName());
// 关键验证:是否为同一个 Class 对象?
System.out.println("clazz1 == clazz2 : " + (clazz1 == clazz2));
System.out.println("clazz2 == clazz3 : " + (clazz2 == clazz3));
}
}

【截图3:Experiment1.java 代码】
五、运行结果与分析
运行后,终端输出如下:
方式1:com.reflection.demo.Student
方式2:com.reflection.demo.Student
方式3:com.reflection.demo.Student
clazz1 == clazz2 : true
clazz2 == clazz3 : true

【截图4:运行结果终端截图】
🔍 结果解读
-
三种方式打印的类名完全相同,都是
com.reflection.demo.Student。 -
最关键的:
clazz1 == clazz2和clazz2 == clazz3都是true。
在 Java 中,==比较的是对象的内存地址。true意味着这三个引用指向堆中同一个对象。
🧠 原理深挖:为什么只有一个 Class 对象?
JVM 的类加载机制保证了:对于同一个类加载器加载的同一个全限定类名,Class 对象在 JVM 中有且仅有一份。
当我们第一次使用 Student 类(无论是 Class.forName、Student.class 还是 new Student() 触发初始化)时,JVM 会:
-
查找
.class文件 -
读取字节码
-
在堆内存中创建一个
java.lang.Class的实例,代表Student类的元数据 -
之后所有对该类的反射或常规操作,都复用这个唯一的
Class对象。
这就是为什么 clazz1、clazz2、clazz3 指向同一块内存。这种设计节约内存,也保证了类型信息的全局一致性。
六、三种方式的适用场景(个人体悟)
| 方式 | 代码示例 | 典型场景 | 优点 | 缺点 |
|---|---|---|---|---|
Class.forName() |
Class.forName("com.demo.Student") |
从配置文件、网络、用户输入中获取类名 | 动态性最强,完全解耦 | 需处理受检异常,字符串拼写错误运行时才发现 |
类名.class |
Student.class |
编译时已知类型,如工具类、静态工厂 | 类型安全,无异常,效率最高 | 硬编码类名,不灵活 |
对象.getClass() |
obj.getClass() |
接收一个 Object 参数,需要知道其实际子类型 | 运行时动态,适合多态场景 | 需要已有对象实例 |
我的体会:刚学反射时,总觉得 Student.class 最简单,直接拿来用就好了。但后来写了一个小型 IOC 容器,从 application.properties 读取 DAO 实现类名时,才发现 Class.forName() 才是真正的“银弹”。它让程序在编写时完全不知道类名,运行时才动态加载——这才是反射最迷人的地方。
另外,很多同学误解 getClass() 和 class 的区别:Student.class 是编译时确定的静态类型;stu.getClass() 是运行时获取的动态类型。当 stu 实际指向一个子类 CollegeStudent 时,getClass() 返回的是 CollegeStudent 的 Class 对象,而不是 Student 的。多态在 Class 层面依然生效。
七、扩展思考:Class 对象能做什么?
拿到 Class 对象只是反射的第一步。后续你可以:
-
通过
newInstance()创建对象(已过时,现用getDeclaredConstructor().newInstance()) -
获取
Method并调用 -
获取
Field并修改值(即使是 private) -
获取
Annotation信息
这些会在后续实验中逐一实现。但请记住:没有 Class 对象,就没有后续的一切。它是一切反射操作的入口。
八、总结与作业反思
本次实验核心收获
-
三种获取
Class对象的方式,各自的适用场景。 -
验证了同一个类在 JVM 中只有一份
Class对象,由类加载器保证。 -
理解了
Class对象是反射的“元数据入口”。
踩坑记录
-
警告
The method secret() is never used locally:这是静态代码检查的误报,因为反射是动态调用,工具无法识别。可以忽略,或加@SuppressWarnings("unused")消除。 -
包结构错误导致 ClassNotFoundException:如果运行
Experiment1时出现ClassNotFoundException,检查Student.java第一行package com.reflection.demo;是否与文件夹路径匹配,以及Experiment1.java是否有import语句。
下一步预告
第二篇实验将使用反射:
-
调用私有构造器创建对象
-
调用私有方法
secret() -
修改私有字段
name的值 -
对比反射调用与直接调用的性能差异
九、参考文献
-
《Java 核心技术 卷Ⅰ》第 5 章:反射
-
Java 官方文档:
java.lang.Class -
《深入理解 Java 虚拟机》第 7 章:类加载机制
写这篇博客时,我特意没有一次把反射全部写完,而是拆成多个小实验。因为反射本身就是个“概念密集”的主题,一次消化太多容易消化不良。建议你也跟着敲一遍代码,把每个输出的含义搞懂,比看十篇文章都有用。
更多推荐

所有评论(0)