什么是反射?
正射:
编译期就确定要操作的类,代码写死
1
2
| User user = new User();
user.setName("张三");
|
特点:编译期类型检查,性能高,灵活性差
反射:
运行期才动态获取类的信息、操作类的成员
1
| Class.forName("com.example.User").getMethod("setName").invoke(obj, "张三");
|
特点:运行期动态解析,性能略低+灵活性极强
反射的底层原理
Java 类加载机制是反射的基础:
.java 源码编译为 .class 字节码文件- 类加载器将
.class 加载到 JVM 内存,生成唯一的 Class 对象(包含类的所有元信息) - 反射就是操作这个
Class 对象;反向获取和操作类的一切
反射相关的主要类
| 类名 | 所在包 | 核心作用 |
|---|
Class | java.lang | 反射的入口,代表一个类的元信息 |
Constructor | java.lang.reflect | 代表类的构造器,用于动态创建对象 |
Field | java.lang.reflect | 代表类的成员变量,用于动态获取 / 修改属性值 |
Method | java.lang.reflect | 代表类的成员方法,用于动态调用方法 |
AccessibleObject | java.lang.reflect | 上述 3 个类的父类,提供关闭访问检查的能力 |
反射的作用(为什么要用反射?)
反射是 Java 生态的基石,没有反射就没有现代 Java 框架,核心作用有 3 个:
1. 实现框架的动态性和可扩展性
这是反射最核心的应用场景:
- Spring IOC:读取配置文件 / 注解中的类全限定名,通过反射动态创建 Bean 并管理
- MyBatis:Mapper 接口的动态代理,通过反射生成实现类,动态调用 SQL
- JDBC 驱动加载:
Class.forName("com.mysql.cj.jdbc.Driver") 就是通过反射加载驱动
2.编写通用工具类
避免为每个类写重复逻辑,实现代码复用:
- 对象属性拷贝:Spring
BeanUtils.copyProperties()、Apache BeanUtils,底层都是反射 - JSON 序列化:Jackson、Gson,通过反射获取对象所有属性;序列化为 JSON
3. 突破封装限制(特殊场景)
可以操作类的 private 成员,处理特殊需求(如单元测试、框架内部逻辑)。
反射的优缺点
优点
- 极强的灵活性:编译期不依赖具体类,运行期动态加载,是框架可扩展性的核心
- 代码复用性高:可以编写通用工具类,避免重复代码
- 突破封装:可以操作私有成员,处理特殊场景
缺点
- 性能损耗:反射是运行期动态解析,比直接调用慢 10~100 倍,频繁使用会严重影响性能
- 破坏封装性:操作私有成员违反了面向对象的封装原则,可能导致安全问题
- 编译期无法检查错误:类、方法、属性是否存在,只有运行期才会抛异常,增加调试难度
- 代码可读性差:反射代码比常规代码复杂,不利于后期维护
反射调用优化(3 个核心手段)
1.缓存反射对象(最有效、最常用)
不要每次都重新获取 Class、Constructor、Method、Field 对象,缓存起来复用,性能可以提升几十倍。
代码示例:优化前后对比
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
| import java.lang.reflect.Method;
public class ReflectOptimization {
// 优化前:每次调用都重新获取Method对象
public static void noCache() throws Exception {
for (int i = 0; i < 100000; i++) {
User user = new User();
Method method = User.class.getMethod("setName", String.class);
method.invoke(user, "张三");
}
}
// 优化后:缓存Method对象,复用
private static final Method SET_NAME_METHOD;
static {
try {
SET_NAME_METHOD = User.class.getMethod("setName", String.class);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
public static void withCache() throws Exception {
for (int i = 0; i < 100000; i++) {
User user = new User();
SET_NAME_METHOD.invoke(user, "张三");
}
}
// 性能测试
public static void main(String[] args) throws Exception {
long start1 = System.currentTimeMillis();
noCache();
System.out.println("无缓存耗时:" + (System.currentTimeMillis() - start1) + "ms");
long start2 = System.currentTimeMillis();
withCache();
System.out.println("有缓存耗时:" + (System.currentTimeMillis() - start2) + "ms");
}
}
|
2.使用 MethodHandle(JDK 7+ 推荐)
MethodHandle 是 JDK 7 引入的轻量级反射 API,性能比传统反射高很多,接近直接调用。
代码示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
public class MethodHandleDemo {
public static void main(String[] args) throws Throwable {
// 1. 获取MethodHandles.Lookup
MethodHandles.Lookup lookup = MethodHandles.lookup();
// 2. 定义方法类型:返回值void,参数String
MethodType methodType = MethodType.methodType(void.class, String.class);
// 3. 查找方法
MethodHandle setNameHandle = lookup.findVirtual(User.class, "setName", methodType);
// 4. 调用方法
User user = new User();
setNameHandle.invoke(user, "李四");
System.out.println(user.getName()); // 输出:李四
}
}
|
避免频繁调用 setAccessible(true)
setAccessible(true) 本身也有性能损耗,只需要调用一次,后续复用即可(配合缓存使用)。
关闭访问检查:setAccessible(true)
作用:
AccessibleObject 是 Constructor、Field、Method 的父类,提供 setAccessible(boolean flag) 方法:
- 默认
false:开启访问检查,无法访问 private 成员 - 设为
true:关闭 JVM 的访问权限检查,可以访问 private 成员(俗称「暴力反射」)
代码示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| import java.lang.reflect.Field;
public class SetAccessibleDemo {
public static void main(String[] args) throws Exception {
User user = new User();
// 获取private属性name
Field nameField = User.class.getDeclaredField("name");
// ❌ 不关闭访问检查,直接操作会抛异常:IllegalAccessException
// nameField.set(user, "张三");
// ✅ 关闭访问检查
nameField.setAccessible(true);
nameField.set(user, "张三");
System.out.println(nameField.get(user)); // 输出:张三
}
}
|
注意事项
- 不是修改访问修饰符:
setAccessible(true) 只是关闭了 JVM 的检查,private 依然是 private,代码层面的访问修饰符没有改变 - 安全管理器限制:如果 JVM 启动了安全管理器(
SecurityManager),可能会禁止调用 setAccessible(true) - 只需要调用一次:配合缓存使用,不要每次都调用
Class类
什么是 Class 类?
当 JVM 加载一个 .class 字节码文件时,会在堆内存中自动生成唯一的一个 Class 对象。这个对象就像类的 “身份证”,包含了该类的所有结构信息,反射就是通过操作这个对象,反向获取和操作类的一切。
Class 类的核心特点
没有公共构造方法
- 不能用
new Class() 创建实例,只能通过 JVM 自动生成或 3 种固定方式获取 - 构造方法是私有的:
private Class(ClassLoader loader)
单例性
- JVM 中,一个类永远只有一个 Class 实例
- 无论用哪种方式获取,得到的都是同一个对象
1
2
3
4
| Class<?> c1 = Class.forName("com.example.User");
Class<?> c2 = User.class;
Class<?> c3 = new User().getClass();
System.out.println(c1 == c2 && c2 == c3); // true
|
不可变性
- Class 对象一旦被 JVM 加载,其内容就不可改变
- 不能通过 Class 对象修改类的结构(只能读取和调用)
会触发类的加载和初始化
Class.forName("全类名") 会触发类的静态代码块执行类名.class 和 对象.getClass() 不会触发静态初始化
Class 类的常用方法及演示
演示对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| package com.example;
public class User {
private String name;
private int age;
public String gender;
public User() {}
private User(String name, int age) { this.name = name; this.age = age; }
public void sayHello() { System.out.println("Hello, " + name); }
private String getSecret() { return "I love Java"; }
// getter/setter
public String getName() { return name; }
public void setName(String name) { this.name = name; }
}
|
获取 Class 对象的方法
这是所有反射操作的第一步,3 种方式各有适用场景:
1.Class.forName(String className)
例:Class.forName("com.example.User")
框架首选,编译期不依赖类,运行期动态加载,会触发静态初始化
2.类名.class
例:User.class
编译期确定,需要导入类包,不会触发静态初始化,多用于参数传递
3.对象.getClass()
例:new User().getClass()
已有对象时使用,能获取对象的实际运行类(适配多态)
创建对象的方法
通过 Class 对象动态创建类的实例:
1.T newInstance():通过无参构造创建对象
JDK9 已废弃,推荐使用其他方法
2.Constructor<T> getConstructor(Class<?>... parameterTypes):获取 public 无参 / 有参构造器
只能获取 public 的
3.Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes):获取任意构造器(包括 private)
配合 setAccessible(true) 使用
代码示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| import java.lang.reflect.Constructor;
public class ClassCreateObjectDemo {
public static void main(String[] args) throws Exception {
Class<User> userClass = User.class;
// 1. 推荐方式:通过public无参构造创建对象
Constructor<User> noArgConstructor = userClass.getConstructor();
User user1 = noArgConstructor.newInstance();
user1.setName("张三");
System.out.println("无参构造创建:" + user1.getName());
// 2. 通过private有参构造创建对象(暴力反射)
Constructor<User> privateConstructor = userClass.getDeclaredConstructor(String.class, int.class);
privateConstructor.setAccessible(true); // 关闭访问检查
User user2 = privateConstructor.newInstance("李四", 20);
System.out.println("私有构造创建:" + user2.getName());
}
}
|
获取构造器的方法
获取类的所有构造器信息:
1.Constructor<?>[] getConstructors():获取所有 public 构造器
2.Constructor<?>[] getDeclaredConstructors():获取所有构造器(包括 private、protected、default)
代码示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| import java.lang.reflect.Constructor;
public class ClassGetConstructorsDemo {
public static void main(String[] args) {
Class<User> userClass = User.class;
System.out.println("所有public构造器:");
Constructor<?>[] publicConstructors = userClass.getConstructors();
for (Constructor<?> constructor : publicConstructors) {
System.out.println("- " + constructor);
}
System.out.println("\n所有构造器(包括私有):");
Constructor<?>[] allConstructors = userClass.getDeclaredConstructors();
for (Constructor<?> constructor : allConstructors) {
System.out.println("- " + constructor);
}
}
}
|
获取成员变量的方法
获取类的所有属性信息:
1.Field[] getFields():获取所有 public 属性
2.Field[] getDeclaredFields():获取所有属性(包括 private)
3.Field getField(String name):获取指定名称的 public 属性
4.Field getDeclaredField(String name):获取指定名称的任意属性(包括 private)
代码示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| import java.lang.reflect.Field;
public class ClassGetFieldsDemo {
public static void main(String[] args) throws Exception {
Class<User> userClass = User.class;
User user = userClass.getConstructor().newInstance();
// 获取并操作private属性
Field nameField = userClass.getDeclaredField("name");
nameField.setAccessible(true);
nameField.set(user, "王五");
System.out.println("name属性值:" + nameField.get(user));
}
}
|
获取成员方法的方法
获取类的所有方法信息:
1.Method[] getMethods():获取所有 public 方法(包括父类继承的)
2.Method[] getDeclaredMethods():获取本类所有方法(包括 private,不包括继承的
3.Method getMethod(String name, Class<?>... parameterTypes):获取指定名称和参数的 public 方法
4.Method getDeclaredMethod(String name, Class<?>... parameterTypes):获取指定名称和参数的任意方法(包括 private)
代码示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| import java.lang.reflect.Method;
public class ClassGetMethodsDemo {
public static void main(String[] args) throws Exception {
Class<User> userClass = User.class;
User user = userClass.getDeclaredConstructor(String.class, int.class).newInstance("赵六", 22);
user.setAccessible(true);
// 调用public方法
Method sayHelloMethod = userClass.getMethod("sayHello");
sayHelloMethod.invoke(user); // 输出:Hello, 赵六
// 调用private方法
Method getSecretMethod = userClass.getDeclaredMethod("getSecret");
getSecretMethod.setAccessible(true);
String secret = (String) getSecretMethod.invoke(user);
System.out.println("私有方法返回值:" + secret);
}
}
|
获取类基本信息的方法
获取类的元数据信息:
1.String getName():获取全类名(包名 + 类名)
2.String getSimpleName():获取简单类名
3.Package getPackage():获取包信息
4.Class<? super T> getSuperclass():获取父类 Class 对象
5.Class<?>[] getInterfaces():获取实现的所有接口
6.boolean isInterface():是否是接口
7.boolean isArray():是否是数组
8.boolean isPrimitive():是否是基本数据类型
代码示例:
1
2
3
4
5
6
7
8
9
10
11
12
| public class ClassInfoDemo {
public static void main(String[] args) {
Class<User> userClass = User.class;
System.out.println("全类名:" + userClass.getName()); // com.example.User
System.out.println("简单类名:" + userClass.getSimpleName()); // User
System.out.println("包名:" + userClass.getPackage().getName()); // com.example
System.out.println("父类:" + userClass.getSuperclass().getName()); // java.lang.Object
System.out.println("是否是接口:" + userClass.isInterface()); // false
System.out.println("是否是数组:" + userClass.isArray()); // false
}
}
|
注意事项:
Class.forName() 会触发静态初始化
- 如果类中有静态代码块,
Class.forName() 会执行它 类名.class 和 对象.getClass() 不会执行静态代码块
基本数据类型的 Class 对象
- 基本数据类型也有对应的 Class 对象:
int.class、boolean.class - 它们的包装类有一个
TYPE 常量,指向对应的基本类型 Class 对象:Integer.TYPE == int.class
数组的 Class 对象
- 数组的 Class 对象是
元素类型[].class:String[].class、int[].class - 所有同类型同维度的数组共享同一个 Class 对象
newInstance() 方法已废弃
- JDK9 之后,
Class.newInstance() 被废弃,因为它会传播未检查的异常 - 推荐使用
Constructor.newInstance() 方法创建对象
1
2
3
4
5
6
7
8
9
| import java.lang.reflect.Constructor;
Class<User> userClass = User.class;
// ✅ 调用者是 Class 对象 → 执行 Class.newInstance()
User user1 = userClass.newInstance();
// ✅ 调用者是 Constructor 对象 → 执行 Constructor.newInstance()
Constructor<User> constructor = userClass.getConstructor();
User user2 = constructor.newInstance();
|
获取 Class 类对象
哪些类型有 Class 对象?
Java 中几乎所有类型都有对应的 Class 对象,JVM 会为每一种类型在内存中生成唯一的 Class 实例。具体包括以下 8 大类:
| 类型分类 | 示例 | Class 对象写法 |
|---|
| 普通类 | User、String、ArrayList | User.class |
| 接口 | List、Runnable、Serializable | List.class |
| 数组 | int[]、String[]、User[][] | int[].class、String[][].class |
| 基本数据类型 | int、boolean、char、double | int.class、boolean.class |
| 枚举 | enum Color {RED, GREEN} | Color.class |
| 注解 | @Override、@Service | Override.class |
| 泛型参数 | T、E | 无法直接获取,通过反射间接获取 |
| void | 无返回值 | void.class |
关键验证:Class 对象的单例性
同一个类型在同一个 JVM 中永远只有一个 Class 实例,无论用哪种方式获取,得到的都是同一个对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| public class ClassSingletonDemo {
public static void main(String[] args) {
// 验证普通类
Class<?> c1 = User.class;
Class<?> c2 = new User().getClass();
System.out.println(c1 == c2); // true
// 验证数组
Class<?> arr1 = int[].class;
Class<?> arr2 = new int[10].getClass();
System.out.println(arr1 == arr2); // true
// 验证基本数据类型
System.out.println(int.class == int.class); // true
}
}
class User {}
|
获取 Class 对象的 3 种核心方法
方法 1:Class.forName(String 全类名)
最灵活、框架首选的方式,编译期不需要依赖类,运行期动态加载。
1
| Class<?> clazz = Class.forName("包名.类名");
|
核心特点
- 会触发类的完整初始化:执行静态代码块、静态变量赋值
- 编译期不依赖类:只要运行时类存在即可,是框架实现可扩展性的核心
- 抛出检查异常:
ClassNotFoundException(类不存在时)
适用场景
- 框架底层动态加载类(Spring IOC、MyBatis、JDBC)
- 配置文件中指定类名,运行时动态创建对象
- 插件化开发
代码示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| public class ForNameDemo {
public static void main(String[] args) {
try {
// 1. 加载普通类
Class<?> userClass = Class.forName("com.example.User");
System.out.println("加载User类成功:" + userClass.getName());
// 2. JDBC 加载驱动(经典应用)
Class.forName("com.mysql.cj.jdbc.Driver");
System.out.println("MySQL驱动加载成功");
} catch (ClassNotFoundException e) {
System.err.println("类不存在:" + e.getMessage());
}
}
}
// 验证静态代码块执行
class User {
static {
System.out.println("User类的静态代码块执行了");
}
}
|
方法 2:类名.class
最安全、性能最高的方式,编译期就确定了 Class 对象。
语法:
1
2
3
| Class<User> userClass = User.class;
Class<Integer> intClass = int.class;
Class<List> listClass = List.class;
|
核心特点
- 不会触发类的初始化:不执行静态代码块,性能最好
- 编译期类型检查:如果类不存在,编译直接报错,更安全
- 支持泛型:可以得到带泛型的 Class 对象,避免类型转换
适用场景
- 方法参数传递(比如获取方法的参数类型、返回值类型)
- 已知类名的情况下,优先使用这种方式
- 基本数据类型只能用这种方式获取 Class 对象
代码示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| public class ClassLiteralDemo {
public static void main(String[] args) {
// 1. 普通类
Class<User> userClass = User.class;
System.out.println("User类:" + userClass.getSimpleName());
// 2. 基本数据类型(只能用这种方式)
Class<Integer> intClass = int.class;
System.out.println("int类型:" + intClass.getName());
// 3. 接口
Class<Runnable> runnableClass = Runnable.class;
System.out.println("Runnable接口:" + runnableClass.getName());
// 4. 数组
Class<String[]> stringArrayClass = String[].class;
System.out.println("String数组:" + stringArrayClass.getName());
// 5. void
Class<Void> voidClass = void.class;
System.out.println("void类型:" + voidClass.getName());
}
}
|
方法 3:对象.getClass()
已有对象时使用,可以获取对象的实际运行时类型(适配多态)。
语法:
1
2
| Object obj = new User();
Class<?> clazz = obj.getClass();
|
核心特点
- 获取的是对象的实际运行类,不是声明的类型(多态场景下非常重要)
- 不会触发类的初始化(因为对象已经创建了)
- 只能用于引用类型,基本数据类型不能使用
适用场景
- 多态场景下,获取对象的真实类型
- 已有对象实例,需要获取其 Class 对象
代码示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| public class GetClassDemo {
public static void main(String[] args) {
// 声明类型是 Animal,实际类型是 Dog
Animal animal = new Dog();
// getClass() 返回实际运行时的类 Dog.class
Class<?> clazz = animal.getClass();
System.out.println("对象的实际类型:" + clazz.getName()); // com.example.Dog
// 对比:类名.class 返回声明的类型 Animal.class
System.out.println("声明的类型:" + Animal.class.getName());
// com.example.Animal
}
}
class Animal {}
class Dog extends Animal {}
|
3 种方法对比与选型
| 对比维度 | Class.forName() | 类名.class | 对象.getClass() |
|---|
| 触发初始化 | ✅ 是 | ❌ 否 | ❌ 否 |
| 编译期检查 | ❌ 否(运行时才检查) | ✅ 是 | ✅ 是 |
| 性能 | 最低 | 最高 | 高 |
| 灵活性 | 最高 | 最低 | 中等 |
| 适用场景 | 框架动态加载、插件化 | 已知类名、参数传递 | 已有对象、多态场景 |
特殊场景下的 Class 对象获取
1. 基本数据类型 vs 包装类的 Class 对象
基本数据类型和其包装类的 Class 对象是不同的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| public class PrimitiveVsWrapperDemo {
public static void main(String[] args) {
// 基本数据类型
Class<Integer> intClass = int.class;
// 包装类
Class<Integer> integerClass = Integer.class;
System.out.println(intClass == integerClass); // false
// 包装类的 TYPE 常量指向对应的基本类型 Class 对象
System.out.println(intClass == Integer.TYPE); // true
System.out.println(boolean.class == Boolean.TYPE); // true
}
}
|
2. 数组的 Class 对象
- 所有同类型、同维度的数组共享同一个 Class 对象
- 不同维度的数组,Class 对象不同
1
2
3
4
5
6
7
8
9
10
| public class ArrayClassDemo {
public static void main(String[] args) {
int[] arr1 = new int[10];
int[] arr2 = new int[100];
int[][] arr3 = new int[5][5];
System.out.println(arr1.getClass() == arr2.getClass()); // true(同类型同维度)
System.out.println(arr1.getClass() == arr3.getClass()); // false(不同维度)
}
}
|
3.匿名内部类的 Class 对象
匿名内部类也有对应的 Class 对象,类名格式为 外部类名$数字:
1
2
3
4
5
6
7
8
9
10
11
| public class AnonymousClassDemo {
public static void main(String[] args) {
Runnable runnable = new Runnable() {
@Override
public void run() {}
};
System.out.println(runnable.getClass().getName());
// 输出:com.example.AnonymousClassDemo$1
}
}
|
常见坑与注意事项
Class.forName() 必须写全类名(包名 + 类名),否则会报 ClassNotFoundException- 基本数据类型只能用
类名.class 获取 Class 对象,不能用 Class.forName() 或 对象.getClass() getClass() 返回的是实际运行时类型,不是声明的类型,多态场景下要特别注意- 数组的 Class 对象与元素类型和维度都有关,不同维度的数组 Class 对象不同
- Class 对象是单例的,可以用
== 比较两个 Class 对象是否相等
类加载
类加载的基本介绍
什么是类加载?
JVM 在第一次使用某个类时,会将该类的.class字节码文件从磁盘(或网络、内存等)读取到内存中,并对其进行验证、准备、解析、初始化,最终生成一个可以被程序使用的Class对象。这个完整的过程,就是类加载。
核心特点:
- 按需加载:JVM 不会一次性加载所有类,只有在第一次使用时才会加载
- 一次加载,终身复用:一个类在 JVM 的生命周期中只会被加载一次,生成的
Class对象会被缓存起来 - 可扩展:通过自定义类加载器,可以实现从任意来源加载类(如网络、加密文件、动态生成的字节码)
类加载与反射的关系
反射的本质是操作 JVM 加载后生成的Class对象。如果一个类没有被 JVM 加载,就无法获取它的Class对象,也就无法进行任何反射操作。
类加载的时机
JVM 只有在主动引用一个类时,才会触发类的初始化(类加载的最后一个阶段)。如果是被动引用,则不会触发类的初始化。
1. 主动引用(一定会触发类加载)
以下 6 种情况属于主动引用,JVM 会立即加载并初始化该类:
- 创建类的实例:
new User() - 调用类的静态成员:访问或修改静态变量、调用静态方法
- 使用反射:
Class.forName("com.example.User") - 初始化子类:初始化子类时,会先初始化其父类
- 启动类:执行
main()方法的类 - JDK 8+ 接口默认方法:如果一个接口定义了默认方法,那么实现该接口的类初始化时,会先初始化该接口
2. 被动引用(不会触发类加载)
以下 3 种情况属于被动引用,JVM 不会触发类的初始化:
- 通过子类引用父类的静态变量:只会初始化父类,不会初始化子类
- 定义类的数组:
User[] users = new User[10]; 只会初始化数组本身,不会初始化User类 - 引用类的常量:常量在编译阶段会被存入调用类的常量池,本质上没有直接引用定义常量的类
代码示例:主动引用 vs 被动引用:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
| public class ClassLoadTimingDemo {
public static void main(String[] args) {
// 被动引用1:通过子类引用父类的静态变量
// 只会输出"父类初始化",不会输出"子类初始化"
System.out.println(Child.parentStaticVar);
// 被动引用2:定义类的数组
// 不会输出任何初始化信息
User[] users = new User[10];
// 被动引用3:引用类的常量
// 不会输出"常量类初始化"
System.out.println(ConstantClass.CONSTANT);
// 主动引用:创建对象
// 会输出"User类初始化"
User user = new User();
}
}
class Parent {
public static int parentStaticVar = 100;
static {
System.out.println("父类初始化");
}
}
class Child extends Parent {
static {
System.out.println("子类初始化");
}
}
class User {
static {
System.out.println("User类初始化");
}
}
class ConstantClass {
public static final String CONSTANT = "hello";
static {
System.out.println("常量类初始化");
}
}
|
类加载的完整过程
类加载分为5 个连续的阶段,其中前 4 个阶段(加载、验证、准备、解析)称为连接阶段,最后一个阶段是初始化阶段。
1
2
3
| 类加载流程:
加载 → 验证 → 准备 → 解析 → 初始化
└────────── 连接阶段 ─┘
|
阶段 1:加载(Loading)
核心任务:找到字节码文件,读取到内存,生成Class对象。
具体完成 3 件事:
- 获取字节码:通过类的全限定名(包名 + 类名),找到对应的
.class字节码文件- 字节码来源:本地磁盘、网络(如 Tomcat 加载 war 包)、动态生成(如 ASM、CGLIB)、加密文件
- 转化数据结构:将字节码的静态二进制结构,转化为方法区的运行时数据结构
- 生成 Class 对象:在堆内存中生成一个对应的
java.lang.Class对象,作为方法区中类数据的访问入口
关键说明:
- 加载阶段由 类加载器(ClassLoader) 完成
- 加载阶段和连接阶段是交叉进行的,加载阶段还没结束,连接阶段可能已经开始
- 数组类的加载比较特殊:数组类本身由 JVM 直接创建,其元素类型由类加载器加载
阶段 2:验证(Verification)
核心任务:确保字节码文件的正确性,防止恶意代码攻击 JVM。
这是 JVM 的安全屏障,确保加载的字节码符合 JVM 规范,不会导致 JVM 崩溃。
验证分为 4 个步骤:
文件格式验证:验证字节码文件的格式是否符合规范
- 检查魔数(
0xCAFEBABE)、版本号、常量池格式等
元数据验证:验证类的元数据信息是否符合 Java 语法规范
- 检查类是否有父类、是否继承了不允许继承的类(如
final类)、方法和字段是否符合语法
字节码验证:最复杂的一步,验证方法的字节码逻辑是否合法
- 检查操作数栈的类型是否正确、跳转指令是否指向正确的位置、类型转换是否合法
符号引用验证:验证常量池中的符号引用是否存在且合法
- 检查符号引用指向的类、方法、字段是否存在,访问权限是否合法
阶段 3:准备(Preparation)
核心任务:为类的静态变量分配内存,并设置默认初始值。
关键细节:
- 只处理静态变量:实例变量的内存分配是在对象创建时(堆中)进行的
- 设置默认初始值:不是代码中写的初始值,而是 JVM 规定的默认值
final修饰的静态常量:在准备阶段就会被赋值为代码中写的初始值(因为常量在编译期就确定了)
| 类型 | 默认初始值 |
|---|
| int | 0 |
| long | 0L |
| float | 0.0f |
| double | 0.0d |
| boolean | false |
| char | ‘\u0000’ |
| 引用类型 | null |
| 代码示例:准备阶段 vs 初始化阶段的赋值 | |
1
2
3
4
5
6
7
8
9
10
11
12
13
| public class PreparePhaseDemo {
// 准备阶段:分配内存,设置默认值0
// 初始化阶段:赋值为100
public static int a = 100;
// 准备阶段:直接赋值为200(final修饰的静态常量)
public static final int b = 200;
public static void main(String[] args) {
System.out.println(a); // 100
System.out.println(b); // 200
}
}
|
阶段 4:解析(Resolution)
核心任务:将常量池中的符号引用,转化为直接引用。
核心概念:
- 符号引用:用一组字符串来描述所引用的目标,比如
com.example.User#sayHello() - 直接引用:指向目标内存地址的指针、偏移量或句柄
解析的主要内容:
- 类或接口的解析
- 字段的解析
- 类方法的解析
- 接口方法的解析
关键说明:
- 解析阶段可以在初始化阶段之后再执行(称为动态解析),这是 Java 实现动态绑定(多态)的基础
- 解析阶段只会执行一次,解析后的直接引用会被缓存起来
阶段 5:初始化(Initialization)
核心任务:执行类的静态变量赋值语句和静态代码块,完成类的初始化。
这是类加载的最后一个阶段,也是我们最熟悉的阶段。
核心执行逻辑:
JVM 会自动生成一个类构造器方法<clinit>(),初始化阶段就是执行这个方法的过程。
<clinit>()方法由编译器自动生成,包含:
- 所有静态变量的赋值语句
- 所有静态代码块(
static{})中的代码
初始化的执行顺序:
- 父类先初始化,子类后初始化:执行子类的
<clinit>()方法前,会先执行父类的<clinit>()方法 - 静态变量和静态代码块按顺序执行:在类中定义的顺序,就是执行的顺序
<clinit>()方法只会执行一次:JVM 会保证<clinit>()方法在多线程环境下被正确加锁和同步,确保一个类只会被初始化一次
代码示例:初始化顺序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| public class InitializationOrderDemo {
public static void main(String[] args) {
new Son();
}
}
class Father {
static {
System.out.println("父类静态代码块");
}
public Father() {
System.out.println("父类构造方法");
}
}
class Son extends Father {
static {
System.out.println("子类静态代码块");
}
public Son() {
System.out.println("子类构造方法");
}
}
|
类加载各阶段任务总结
| 阶段 | 核心任务 | 关键细节 |
|---|
| 加载 | 读取字节码,生成 Class 对象 | 由类加载器完成,字节码来源多样 |
| 验证 | 确保字节码的正确性和安全性 | 4 个验证步骤,JVM 的安全屏障 |
| 准备 | 为静态变量分配内存,设置默认初始值 | final 静态常量直接赋值,实例变量不处理 |
| 解析 | 符号引用转化为直接引用 | 支持动态解析,是多态的基础 |
| 初始化 | 执行静态变量赋值和静态代码块 | 执行<clinit>()方法,父类先初始化 |
常见问题与注意事项
- 类加载的顺序:静态代码块 → 构造代码块 → 构造方法
- 静态变量的赋值:准备阶段赋默认值,初始化阶段赋代码中的值
- 被动引用不会触发初始化:这是很多面试题的考点
<clinit>() vs <init>():<clinit>():类构造器,初始化类的静态成员,只执行一次<init>():对象构造器,初始化对象的实例成员,每次创建对象都会执行
- 类加载器的双亲委派模型:JVM 默认采用双亲委派模型加载类,确保核心类的安全性
通过反射获取类的结构信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
| // 父类
class Base {
public String basePublicField;
private int basePrivateField;
public void basePublicMethod() {}
private void basePrivateMethod() {}
}
// 子类(演示用)
@Deprecated
public class Student extends Base {
// 不同修饰符的属性
public String name;
protected int age;
private String address;
public static final String SCHOOL = "清华大学";
// 不同修饰符的构造器
public Student() {}
public Student(String name) { this.name = name; }
private Student(String name, int age) { this.name = name; this.age = age; }
// 不同修饰符的方法
public void study() {}
protected void eat() {}
private void sleep() {}
public static void run() {}
}
|
java.lang.Class 类
![[Pasted image 20260407211101.png]]
所有获取类结构信息的操作,都从 Class 对象开始。
代码示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
| import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class ReflectionUtilsDemo {
public static void main(String[] args) throws Exception {
Class<Student> studentClass = Student.class;
// 1. getName: 获取全类名(包名+类名)
System.out.println("1. 全类名:" + studentClass.getName());
// 输出:com.hspedu.reflection.Student
// 2. getSimpleName: 获取简单类名
System.out.println("\n2. 简单类名:" + studentClass.getSimpleName());
// 输出:Student
// 3. getFields: 获取所有public修饰的属性,包含本类以及父类的
// 不能访问私有成员
System.out.println("\n3. 所有public属性(含父类):");
Field[] publicFields = studentClass.getFields();
for (Field field : publicFields) {
System.out.println("- " + field.getName());
}
// 输出:name, SCHOOL, basePublicField
// 4. getDeclaredFields: 获取本类中所有属性(不包含父类)
// 配合 setAccessible(true)能访问私有成员
System.out.println("\n4. 本类所有属性(不含父类):");
Field[] allFields = studentClass.getDeclaredFields();
for (Field field : allFields) {
System.out.println("- " + field.getName());
}
// 输出:name, age, address, SCHOOL
// 5. getMethods: 获取所有public修饰的方法,包含本类以及父类的
System.out.println("\n5. 所有public方法(含父类):");
Method[] publicMethods = studentClass.getMethods();
for (Method method : publicMethods) {
System.out.println("- " + method.getName());
}
// 输出:study, run, basePublicMethod, wait, equals, toString, hashCode...(Object类的方法)
// 6. getDeclaredMethods: 获取本类中所有方法(不包含父类)
System.out.println("\n6. 本类所有方法(不含父类):");
Method[] allMethods = studentClass.getDeclaredMethods();
for (Method method : allMethods) {
System.out.println("- " + method.getName());
}
// 输出:study, eat, sleep, run
// 7. getConstructors: 获取本类所有public修饰的构造器
System.out.println("\n7. 所有public构造器:");
Constructor<?>[] publicConstructors = studentClass.getConstructors();
for (Constructor<?> constructor : publicConstructors) {
System.out.println("- " + constructor.getName());
}
// 输出:com.hspedu.reflection.Student(无参)、com.hspedu.reflection.Student(一个参数)
// 8. getDeclaredConstructors: 获取本类中所有构造器
System.out.println("\n8. 本类所有构造器:");
Constructor<?>[] allConstructors = studentClass.getDeclaredConstructors();
for (Constructor<?> constructor : allConstructors) {
System.out.println("- " + constructor.getName());
}
// 输出:无参、一个参数、两个参数(私有)
// 9. getPackage: 以Package形式返回包信息
System.out.println("\n9. 包信息:" + studentClass.getPackage().getName());
// 输出:com.hspedu.reflection
// 10. getSuperClass: 以Class形式返回父类信息
System.out.println("\n10. 父类信息:" + studentClass.getSuperclass().getName());
// 输出:com.hspedu.reflection.Base
// 11. getInterfaces: 以Class[]形式返回接口信息
System.out.println("\n11. 实现的接口:");
Class<?>[] interfaces = studentClass.getInterfaces();
for (Class<?> anInterface : interfaces) {
System.out.println("- " + anInterface.getName());
}
// 输出:无(Student类没有实现接口)
// 12. getAnnotations: 以Annotation[]形式返回注解信息
System.out.println("\n12. 类上的注解:");
Annotation[] annotations = studentClass.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println("- " + annotation.annotationType().getName());
}
// 输出:java.lang.Deprecated
}
}
|
java.lang.reflect.Field 类
用于获取和操作类的属性信息。
![[Pasted image 20260407211739.png]]
代码示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
public class FieldDemo {
public static void main(String[] args) {
Class<Student> studentClass = Student.class;
Field[] allFields = studentClass.getDeclaredFields();
for (Field field : allFields) {
System.out.println("属性名:" + field.getName());
// 1. getModifiers: 以int形式返回修饰符
int modifiers = field.getModifiers();
// 将int值转换为对应的修饰符字符串
String modifierStr = Modifier.toString(modifiers);
System.out.println("修饰符:" + modifierStr + "(int值:" + modifiers + ")");
// 2. getType: 以Class形式返回属性类型
Class<?> type = field.getType();
System.out.println("属性类型:" + type.getName());
System.out.println("------------------------");
}
}
}
|
多个修饰符是相加的关系:public(1) + static(8) + final(16) = 25
| 修饰符 | int 值 |
|---|
| 默认(包访问权限) | 0 |
| public | 1 |
| private | 2 |
| protected | 4 |
| static | 8 |
| final | 16 |
java.lang.reflect.Method 类
用于获取和调用类的方法信息。
![[Pasted image 20260407211751.png]]
代码示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
| import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
public class MethodDemo {
public static void main(String[] args) {
Class<Student> studentClass = Student.class;
Method[] allMethods = studentClass.getDeclaredMethods();
for (Method method : allMethods) {
System.out.println("方法名:" + method.getName());
// 1. getModifiers: 以int形式返回修饰符
int modifiers = method.getModifiers();
String modifierStr = Modifier.toString(modifiers);
System.out.println("修饰符:" + modifierStr);
// 2. getReturnType: 以Class形式获取返回类型
Class<?> returnType = method.getReturnType();
System.out.println("返回类型:" + returnType.getName());
// 3. getParameterTypes: 以Class[]返回参数类型数组
Class<?>[] parameterTypes = method.getParameterTypes();
System.out.print("参数类型:");
for (Class<?> parameterType : parameterTypes) {
System.out.print(parameterType.getName() + " ");
}
System.out.println("\n------------------------");
}
}
}
|
java.lang.reflect.Constructor 类
用于获取和调用类的构造器信息。
![[Pasted image 20260407211802.png]]
代码示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
public class ConstructorDemo {
public static void main(String[] args) {
Class<Student> studentClass = Student.class;
Constructor<?>[] allConstructors = studentClass.getDeclaredConstructors();
for (Constructor<?> constructor : allConstructors) {
System.out.println("构造器名:" + constructor.getName());
// 1. getModifiers: 以int形式返回修饰符
int modifiers = constructor.getModifiers();
String modifierStr = Modifier.toString(modifiers);
System.out.println("修饰符:" + modifierStr);
// 2. getParameterTypes: 以Class[]返回参数类型数组
Class<?>[] parameterTypes = constructor.getParameterTypes();
System.out.print("参数类型:");
for (Class<?> parameterType : parameterTypes) {
System.out.print(parameterType.getName() + " ");
}
System.out.println("\n------------------------");
}
}
}
|