Java反射机制(一):深入理解Class对象——从三种获取方式看JVM的类加载原理

发布时间:2026/5/26 3:39:18

Java反射机制(一):深入理解Class对象——从三种获取方式看JVM的类加载原理 一、引言为什么我们需要反射在常规 Java 编程中我们写的代码在编译期就要确定类型new Student()、student.study()。一切都必须是明确已知的。但如果我告诉你类的名字在编译时根本不存在而是写在配置文件里程序运行时才能读到比如payServicecom.mybank.WeChatPayService你该怎么办用new是做不到的因为你不知道类名。这时候Java 的反射机制就出场了——它允许程序在运行时动态获取一个类的完整信息构造器、方法、字段并动态调用它们。反射是框架Spring、MyBatis、Hibernate的底层基石。理解了反射你就拿到了看懂框架源码的第一把钥匙。二、实验目标与准备本次实验只做一件事用三种不同的方式拿到一个类的Class对象并验证它们其实是同一个对象。环境准备JDK 17VS Code Java 扩展包项目结构java_reflection_blog/ └── src/ ├── com/reflection/demo/Student.java └── Experiment1.java【截图1VS 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 }; } }【截图2Student.java 完整代码】这里故意写了一个私有方法secret()目的是为后续实验展示setAccessible(true)的威力。但目前第一步只关注Class对象所以暂时不用它。四、实验核心三种方式获取 Class 对象新建Experiment1.javaimport 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 —— 编译时已知最简洁 ClassStudent 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)); } }【截图3Experiment1.java 代码】五、运行结果与分析运行后终端输出如下方式1com.reflection.demo.Student 方式2com.reflection.demo.Student 方式3com.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)从配置文件、网络、用户输入中获取类名动态性最强完全解耦需处理受检异常字符串拼写错误运行时才发现类名.classStudent.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 章类加载机制写这篇博客时我特意没有一次把反射全部写完而是拆成多个小实验。因为反射本身就是个“概念密集”的主题一次消化太多容易消化不良。建议你也跟着敲一遍代码把每个输出的含义搞懂比看十篇文章都有用。

相关新闻