背景
本文是《Java 后端从小白到大神》修仙系列第十六篇,正式进入Java后端世界,本篇文章主要聊Java基础中的反射与序列化。若想详细学习请点击 首篇博文,我们开始吧。
文章概览
- 反射机制详解
- 序列化与反序列化
- 反射与序列化的结合应用
- 代码示例与最佳实践
一、反射机制详解
1.1 什么是反射?
反射(Reflection) 是 Java 提供的一种能力,允许程序在运行时动态获取类的信息(如类名、方法、字段、注解等)并操作类或对象。
核心价值:
- 动态性:突破编译时的类型限制
- 灵活性:运行时决定调用哪个方法
- 元编程:操作类的元数据
1.2 反射的核心类
| 类名 |
功能 |
主要方法 |
Class<T> |
类的元数据入口 |
forName(), getConstructor(), getMethod(), getField() |
Constructor<T> |
构造器 |
newInstance(), setAccessible() |
Method |
方法 |
invoke(), setAccessible() |
Field |
字段 |
get(), set(), setAccessible() |
Annotation |
注解 |
annotationType(), value() |
Modifier |
修饰符 |
isPublic(), isPrivate(), isStatic() |
1.3 获取 Class 对象的方式
同一个类,在 JVM 里只有一个 Class 对象,所以三种方式拿到的都是同一个对象。
- 通过 类名.class 获取(最安全、最常用)
1
2
3
|
// 编译期就能确定,不会抛出异常
Class<?> clazz1 = String.class;
System.out.println("方式1: " + clazz1.getName());
|
- 通过 对象.getClass() 获取
1
2
3
4
|
// 必须先有实例对象,才能调用
String str = "Hello";
Class<?> clazz2 = str.getClass();
System.out.println("方式2: " + clazz2.getName());
|
- 通过 Class.forName(全类名) 获取(反射常用)
1
2
3
|
// 运行时动态加载,需要写完整包名+类名,会抛出检查异常
Class<?> clazz3 = Class.forName("java.lang.String");
System.out.println("方式3: " + clazz3.getName());
|
- 三个 Class 对象是同一个
1
2
3
|
// 同一个类在 JVM 中只有一个 Class 对象
System.out.println("clazz1 == clazz2: " + (clazz1 == clazz2)); // true
System.out.println("clazz1 == clazz3: " + (clazz1 == clazz3)); // true
|
1.4 创建对象实例
先拿到 Class 对象,再通过反射创建实例。
1
|
Class<?> stringClass = String.class;
|
- 使用无参构造器
1
2
3
4
5
|
// Class.newInstance() 在 Java9+ 已过时
// 底层调用 无参构造
@SuppressWarnings("deprecation")
String str1 = (String) stringClass.newInstance();
System.out.println("无参构造器: " + str1);
|
- 使用带参构造器
1
2
3
4
5
6
|
// 获取指定参数类型的构造器. String(String original)
Constructor<?> constructor = stringClass.getConstructor(String.class);
// 通过构造器创建对象
String str2 = (String) constructor.newInstance("Hello, Reflection!");
System.out.println("带参构造器: " + str2);
|
- 其他类带参构造示例
1
2
3
4
5
6
|
// 获取 Integer 的 String 参数构造器
Constructor<?> intConstructor = Integer.class.getConstructor(String.class);
// 创建 Integer 实例
Integer num = (Integer) intConstructor.newInstance("123");
System.out.println("带参构造创建Integer: " + num);
|
1.5 调用方法
- 调用静态方法
静态方法调用时,invoke 第一个参数传 null
1
2
3
4
5
6
7
8
9
|
// 获取 Class 对象
Class<?> mathClass = Math.class;
// 获取方法:方法名 + 参数类型
Method sqrtMethod = mathClass.getDeclaredMethod("sqrt", double.class);
// 调用:invoke(对象, 参数) → 静态方法对象为 null
Object result1 = sqrtMethod.invoke(null, 25.0);
System.out.println("Math.sqrt(25.0): " + result1);
|
- 调用实例方法
必须传入具体对象,才能调用实例方法
1
2
3
4
5
6
7
8
|
String str = "Hello";
// 获取 substring(int, int) 方法
Method substringMethod = String.class.getDeclaredMethod("substring", int.class, int.class);
// 调用:invoke(对象, 起始索引, 结束索引)
Object result2 = substringMethod.invoke(str, 1, 4);
System.out.println("str.substring(1, 4): " + result2);
|
- 调用私有方法
必须先 setAccessible(true) 打破访问权限
1
2
3
4
5
6
7
8
9
10
11
|
Person person = new Person("Alice");
// 获取私有方法
Method privateMethod = Person.class.getDeclaredMethod("privateMethod");
// 开启权限访问
privateMethod.setAccessible(true);
// 调用私有方法
Object result3 = privateMethod.invoke(person);
System.out.println("私有方法调用: " + result3);
|
- 内部类 Person(用于演示私有方法)
1
2
3
4
5
6
7
8
9
10
11
|
static class Person {
private String name;
public Person(String name) {
this.name = name;
}
private String privateMethod() {
return "Private method called by " + name;
}
}
|
1.6 操作字段
- 先创建对象
类 Person
1
2
3
4
5
6
7
8
9
10
|
static class Person {
public String name;
private int age;
public static String staticField = "Static Value";
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
|
1
|
Person person = new Person("Alice", 20);
|
- 获取并修改公有字段
1
2
3
4
5
6
7
8
9
|
// 获取公有字段 name
Field nameField = Person.class.getDeclaredField("name");
// 获取字段值
System.out.println("原始 name: " + nameField.get(person));
// 修改字段值
nameField.set(person, "Bob");
System.out.println("修改后 name: " + nameField.get(person));
|
- 获取并修改私有字段
私有字段必须先开启权限:setAccessible(true)
1
2
3
4
5
6
7
8
9
10
|
// 获取私有字段 age
Field ageField = Person.class.getDeclaredField("age");
// 打破访问检查
ageField.setAccessible(true);
// 获取并修改
System.out.println("原始 age: " + ageField.get(person));
ageField.set(person, 25);
System.out.println("修改后 age: " + ageField.get(person));
|
- 操作静态字段
静态字段属于类,get/set 时对象传 null
1
2
|
Field staticField = Person.class.getDeclaredField("staticField");
System.out.println("静态字段: " + staticField.get(null));
|
1.7 反射的性能优化
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
|
/**
* 反射性能优化示例
*/
public class ReflectionPerformanceDemo {
// 缓存 Class 对象
private static final Class<?> STRING_CLASS = String.class;
// 缓存方法对象
private static Method concatMethod;
static {
try {
// 在静态初始化块中获取方法,避免重复查找
concatMethod = STRING_CLASS.getDeclaredMethod("concat", String.class);
concatMethod.setAccessible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws Exception {
long start = System.nanoTime();
String str = "Hello";
for (int i = 0; i < 100000; i++) {
// 直接调用(基准测试)
// str.concat(" World");
// 反射调用(优化后)
concatMethod.invoke(str, " World");
}
long end = System.nanoTime();
System.out.println("耗时: " + (end - start) / 1000000 + " ms");
}
}
|
二、序列化与反序列化
2.1 什么是序列化?
序列化是将对象转换为字节流的过程,用于:
- 网络传输(如 RPC、Socket 通信)
- 持久化存储(如文件、数据库 BLOB)
- 进程间通信
反序列化是将字节流恢复为对象的过程。
序列化中 JVM 自动调用的方法清单
writeObject() —— 序列化时自动调用
1
|
private void writeObject(ObjectOutputStream oos) throws IOException
|
触发时机:oos.writeObject(obj)
作用:自定义序列化逻辑
readObject() —— 反序列化时自动调用
1
|
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException
|
触发时机:ois.readObject()
作用:自定义反序列化逻辑
writeReplace() —— 序列化前自动替换对象
1
|
private Object writeReplace() throws ObjectStreamException
|
触发时机:writeObject 之前
作用:返回一个“替身对象”进行序列化(序列化代理模式核心)
readResolve() —— 反序列化后自动替换对象
1
|
private Object readResolve() throws ObjectStreamException
|
触发时机:readObject 之后
作用:返回最终真正的对象(单例/代理模式必用)
writeExternal() —— Externalizable 序列化
1
|
public void writeExternal(ObjectOutput out) throws IOException
|
触发时机:序列化时
作用:手动控制序列化字段
readExternal() —— Externalizable 反序列化
1
|
public void readExternal(ObjectInput in) throws IOException
|
触发时机:反序列化时
作用:手动控制反序列化字段
2.2 核心接口与类
| 接口/类 |
功能 |
主要方法 |
Serializable |
标记接口,表示类可序列化 |
无方法,仅作标记 |
Externalizable |
扩展序列化接口,需手动实现 |
writeExternal(), readExternal() |
ObjectOutputStream |
序列化输出流 |
writeObject(), writeInt() |
ObjectInputStream |
反序列化输入流 |
readObject(), readInt() |
transient |
关键字,标记字段不参与序列化 |
- |
serialVersionUID |
序列化版本号 |
静态常量 |
2.3 基本序列化
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
|
/**
* 基本序列化示例
*/
public class BasicSerializationDemo {
public static void main(String[] args) {
// 序列化
try (ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("person.ser"))) { // .ser = 序列化文件
Person person = new Person("Alice", 20, "password123");
oos.writeObject(person);
System.out.println("序列化完成");
} catch (IOException e) {
e.printStackTrace();
}
// 反序列化
try (ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("person.ser"))) {
Person person = (Person) ois.readObject();
System.out.println("反序列化完成");
System.out.println("Name: " + person.getName());
System.out.println("Age: " + person.getAge());
System.out.println("Password: " + person.getPassword()); // null(transient 字段)
} catch (Exception e) {
e.printStackTrace();
}
}
static class Person implements Serializable {
private static final long serialVersionUID = 1L; // 显式定义版本号
private String name;
private int age;
private transient String password; // 敏感数据不序列化
public Person(String name, int age, String password) {
this.name = name;
this.age = age;
this.password = password;
}
// Getters (略)
public String getName() { return name; }
public int getAge() { return age; }
public String getPassword() { return password; }
}
}
|
2.4 自定义序列化
自定义序列化的 writeObject / readObject,不是你手动调用的!是 JVM 底层自动调用的!
1
2
|
private void writeObject(ObjectOutputStream oos) throws IOException { ... }
private void readObject(ObjectInputStream ois) throws IOException { ... }
|
它们是:固定签名的钩子方法(Hook)
- 权限必须是
private
- 方法名必须是
writeObject / readObject
- 参数必须固定
只要写了,Java 序列化机制自动识别,执行 writeObject() 时会自动调用你写的这个方法!
底层实际发生:
1
2
3
4
5
6
|
oos.writeObject(user)
↓ (JVM 内部自动走)
判断 user 类有没有自己写 writeObject()
↓
有 → 自动调用你写的那个 private 方法
没有 → 走默认序列化
|
defaultWriteObject() = 自动序列化非静态、非 transient 字段
writeObject(加密密码) = 手动追加写入加密后的密码
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
|
/**
* 自定义序列化示例
*/
public class CustomSerializationDemo {
public static void main(String[] args) {
// 序列化
try (ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("user.ser"))) {
User user = new User("Alice", "secret123");
oos.writeObject(user);
System.out.println("序列化完成");
} catch (IOException e) {
e.printStackTrace();
}
// 反序列化
try (ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("user.ser"))) {
User user = (User) ois.readObject();
System.out.println("反序列化完成");
System.out.println("Username: " + user.getUsername());
System.out.println("Password: " + user.getPassword());
} catch (Exception e) {
e.printStackTrace();
}
}
static class User implements Serializable {
private static final long serialVersionUID = 1L;
private String username;
private String password;
public User(String username, String password) {
this.username = username;
this.password = password;
}
// 自定义序列化方法
private void writeObject(ObjectOutputStream oos) throws IOException {
oos.defaultWriteObject(); // 默认序列化
// 对密码进行简单加密(实际应用中应使用更安全的加密方式)
String encryptedPassword = password != null ?
new StringBuilder(password).reverse().toString() : null;
oos.writeObject(encryptedPassword);
}
// 自定义反序列化方法
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
ois.defaultReadObject(); // 默认反序列化
// 解密密码
String encryptedPassword = (String) ois.readObject();
password = encryptedPassword != null ?
new StringBuilder(encryptedPassword).reverse().toString() : null;
}
// Getters (略)
public String getUsername() { return username; }
public String getPassword() { return password; }
}
}
|
2.5 Externalizable 接口
Externalizable 接口提供更细粒度的序列化控制。
Product 实现了 Externalizable 接口
当执行:
1
|
oos.writeObject(product);
|
JVM 内部一检查:
哦,这是个 Externalizable 对象
那我自动调用它的 writeExternal()
反序列化时:
JVM 就自动调用:
implements Externalizable
→ 接口强制要求重写 writeExternal / readExternal
→ JVM 直接调用接口方法
本质一样:都是底层自动回调,你不用显式调用。
默认序列化:
JVM 全包,你不能插手,不能控制,不能优化。
Externalizable序列化:
自己亲手写每一个字段的读写
存什么、怎么存、顺序、加密、过滤、优化 → 全部自己说了算!
这就叫:精细化序列化
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
|
import java.io.*;
/**
* Externalizable 示例
* 特点:手动精细化控制序列化(自己写每个字段的读写逻辑)
*/
public class ExternalizableDemo {
public static void main(String[] args) {
// --------------- 序列化 ---------------
try (ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("product.ser"))) {
Product product = new Product("Laptop", 9999.99);
oos.writeObject(product); // JVM 自动调用 writeExternal()
System.out.println("序列化完成");
} catch (IOException e) {
e.printStackTrace();
}
// --------------- 反序列化 ---------------
try (ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("product.ser"))) {
Product product = (Product) ois.readObject(); // JVM 自动调用 readExternal()
System.out.println("反序列化完成");
System.out.println("Name: " + product.getName());
System.out.println("Price: " + product.getPrice());
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 实现 Externalizable 接口
* 完全由自己手动控制序列化 → 【精细化序列化】
*/
static class Product implements Externalizable {
private String name;
private double price;
/**
* 必须有无参构造器!
* Externalizable 反序列化时强制需要
*/
public Product() {
}
public Product(String name, double price) {
this.name = name;
this.price = price;
}
/**
* 【精细化核心】
* 手动决定:序列化哪些字段、顺序、格式
* JVM 自动调用,不需要手动写代码调用
*/
@Override
public void writeExternal(ObjectOutput out) throws IOException {
// 手动写字段(自己控制顺序、控制存储内容)
out.writeUTF(name);
out.writeDouble(price);
}
/**
* 【精细化核心】
* 手动决定:怎么读取、读取顺序、如何赋值
* JVM 自动调用
*/
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
// 必须和 writeExternal 写入顺序完全一致
name = in.readUTF();
price = in.readDouble();
}
// Getters
public String getName() { return name; }
public double getPrice() { return price; }
}
}
|
2.6 版本兼容性
serialVersionUID 用于确保序列化版本的兼容性:
- 未显式定义:JVM 会根据类结构自动生成
- 显式定义:建议始终显式定义,避免类结构变化导致反序列化失败
版本兼容性规则:
- 修改字段类型:可能导致反序列化失败
- 新增字段:反序列化时会赋予默认值
- 删除字段:反序列化时会忽略不存在的字段
2.7 序列化的替代方案
| 方案 |
优点 |
缺点 |
主流Java工具 & 核心用法 |
| JSON |
轻量、易读、跨语言 |
性能略低,无类型 |
Jackson / Gson 序列化:toJSONString(obj) 反序列化:parseObject(json) |
| XML |
标准、结构清晰 |
体积大、解析慢 |
JAXB / Jackson XML 自动转对象/XML |
| Protobuf |
性能极高、体积极小 |
学习成本高、二进制 |
Protobuf Java 自动生成代码,直接 toByteArray() |
| MessagePack |
高性能、体积小 |
二进制不可读 |
MessagePack-Java 类似JSON,但更快更小 |
| Avro |
schema 灵活、兼容强 |
依赖schema |
Apache Avro 序列化二进制数据流 |
- JSON(最常用,90%项目都用它)
工具:Jackson(Spring默认)、Gson
作用:接口传参、前后端交互、配置文件
1
2
3
4
5
|
// 序列化
String json = new ObjectMapper().writeValueAsString(user);
// 反序列化
User user = new ObjectMapper().readValue(json, User.class);
|
- XML
工具:JAXB、Jackson XML
作用:老项目、接口报文、配置文件
XML(JAXB 示例)
1
2
3
4
5
6
7
8
|
// 序列化:对象 → XML
JAXBContext context = JAXBContext.newInstance(User.class);
Marshaller marshaller = context.createMarshaller();
marshaller.marshal(user, new File("user.xml"));
// 反序列化:XML → 对象
Unmarshaller unmarshaller = context.createUnmarshaller();
User user = (User) unmarshaller.unmarshal(new File("user.xml"));
|
- Protobuf(高性能首选)
工具:Protobuf
作用:微服务、高性能通信、游戏、大数据
特点:速度最快、体积最小
1
2
3
4
5
6
7
|
// 先写 .proto 文件,自动生成 UserProto 类
// 序列化:对象 → byte[]
byte[] data = userProto.toByteArray();
// 反序列化:byte[] → 对象
UserProto user = UserProto.parseFrom(data);
|
- MessagePack
工具:MessagePack
作用:高性能二进制JSON
1
2
3
4
5
6
7
|
ObjectMapper mapper = new ObjectMapper(new MessagePackFactory());
// 序列化
byte[] bytes = mapper.writeValueAsBytes(user);
// 反序列化
User user = mapper.readValue(bytes, User.class);
|
- Avro
工具:Apache Avro
作用:大数据、Hadoop、Kafka
1
2
3
4
5
6
7
8
9
10
11
|
// 序列化:对象 → 字节
ByteArrayOutputStream out = new ByteArrayOutputStream();
DatumWriter<User> writer = new SpecificDatumWriter<>(User.class);
Encoder encoder = EncoderFactory.get().binaryEncoder(out, null);
writer.write(user, encoder);
encoder.flush();
// 反序列化:字节 → 对象
DatumReader<User> reader = new SpecificDatumReader<>(User.class);
Decoder decoder = DecoderFactory.get().binaryDecoder(out.toByteArray(), null);
User user = reader.read(null, decoder);
|
三、反射与序列化的结合应用
3.1 动态对象创建
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
|
/**
* 通过反射和序列化创建动态对象
*/
public class DynamicObjectCreation {
public static void main(String[] args) throws Exception {
// 通过反射创建对象
Class<?> clazz = Class.forName("com.example.User");
Object obj = clazz.newInstance();
// 通过反射设置字段
Field nameField = clazz.getDeclaredField("name");
nameField.setAccessible(true);
nameField.set(obj, "Alice");
Field ageField = clazz.getDeclaredField("age");
ageField.setAccessible(true);
ageField.set(obj, 30);
// 序列化对象
byte[] serialized = serialize(obj);
System.out.println("序列化字节长度: " + serialized.length);
// 反序列化对象
Object deserialized = deserialize(serialized);
System.out.println("反序列化对象: " + deserialized);
}
// 序列化方法
public static byte[] serialize(Object obj) throws IOException {
try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos)) {
oos.writeObject(obj);
return bos.toByteArray();
}
}
// 反序列化方法
public static Object deserialize(byte[] data) throws Exception {
try (ByteArrayInputStream bis = new ByteArrayInputStream(data);
ObjectInputStream ois = new ObjectInputStream(bis)) {
return ois.readObject();
}
}
// 示例类
static class User implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private int age;
@Override
public String toString() {
return "User{name='" + name + "', age=" + age + "}";
}
}
}
|
3.2 通用对象拷贝
序列化只是存数据,反序列化 = JVM 自动 new 一个新对象 + 填数据,所以一定是新对象,一定是深拷贝!
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
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
|
import java.io.*;
/**
* 通过序列化实现深拷贝
* 深拷贝:拷贝出一个完全独立的新对象,修改新对象【不会影响】原对象
*/
public class DeepCopyDemo {
public static void main(String[] args) throws Exception {
// 1. 创建原始对象
Person original = new Person("Alice", 20);
original.setAddress(new Address("Beijing", "China"));
System.out.println("原始对象: " + original);
// 2. 调用深拷贝方法,得到一个【全新的、独立的】Person对象
Person copy = (Person) deepCopy(original);
System.out.println("拷贝对象: " + copy);
// ==========================================
// 关键测试:修改【拷贝对象】的属性
// 深拷贝 → 修改 copy 不会影响 original
// ==========================================
copy.setName("Bob"); // 修改拷贝对象的名字
copy.getAddress().setCity("Shanghai");// 修改拷贝对象的地址城市
// 输出结果:原始对象 不变!拷贝对象 变了!
System.out.println("修改后 - 原始对象: " + original);
System.out.println("修改后 - 拷贝对象: " + copy);
}
/**
* 序列化深拷贝核心方法
* 原理:
* 1. 将对象序列化成字节数组(写到内存流)
* 2. 再从字节数组反序列化为新对象
* 最终得到一个【完全独立的新对象】
*/
@SuppressWarnings("unchecked")
public static <T> T deepCopy(T object) throws Exception {
if (object == null) {
return null;
}
// 第一步:序列化对象 → 内存字节流
try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos)) {
// 将对象写入流(序列化)
oos.writeObject(object);
// 第二步:从内存字节流 → 反序列化为新对象
try (ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis)) {
// 反序列化,返回【全新对象】
return (T) ois.readObject();
}
}
}
/**
* 人类
* 必须实现 Serializable,否则无法序列化
*/
static class Person implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private int age;
private Address address; // 引用类型成员
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// getter & setter
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
public Address getAddress() { return address; }
public void setAddress(Address address) { this.address = address; }
@Override
public String toString() {
return "Person{name='" + name + "', age=" + age + ", address=" + address + "}";
}
}
/**
* 地址类
* 也必须实现 Serializable,因为 Person 引用了它
*/
static class Address implements Serializable {
private static final long serialVersionUID = 1L;
private String city;
private String country;
public Address(String city, String country) {
this.city = city;
this.country = country;
}
// getter & setter
public String getCity() { return city; }
public void setCity(String city) { this.city = city; }
public String getCountry() { return country; }
public void setCountry(String country) { this.country = country; }
@Override
public String toString() {
return "Address{city='" + city + "', country='" + country + "'}";
}
}
}
|
3.3 序列化代理模式
谁的方法,this 就是谁,任何非静态方法:
那么方法内部的 this 永远 == 前面那个 对象
这是 Java 规定死的,不是我编的,不是虚拟机编的。
看下面这段代码,序列化时,JVM 是这样干活的:
1
2
3
4
5
6
7
|
static class ImmutablePerson implements Serializable {
// 这是【成员方法】
private Object writeReplace() throws ObjectStreamException {
return new SerializationProxy(this); // ← this
}
}
|
- JVM 拿到
person 对象
- JVM 检查:这个 person 有没有 writeReplace() 方法?
- 有!
- JVM 自己调用:person.writeReplace()
- 根据Java 规定死的,所以方法里的
this = person
代码示例整体流程
序列化时:
1
2
3
4
5
6
|
调用:oos.writeObject(person)
↓(JVM 自动检查)
发现 person 里有 writeReplace() 方法
↓
JVM 不序列化 person
而是序列化:writeReplace() 返回的 SerializationProxy
|
反序列化时:
1
2
3
4
5
6
7
8
9
|
调用:ois.readObject()
↓
JVM 先读到 SerializationProxy 对象
↓
发现代理类有 readResolve()
↓
调用它 → 返回 new ImmutablePerson(...)
↓
最终你得到的是 ImmutablePerson 对象
|
代码示例
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
93
94
95
96
97
98
99
|
import java.io.*;
import java.util.Objects;
/**
* 序列化代理模式
*/
public class SerializationProxyDemo {
public static void main(String[] args) throws Exception {
// 1. 创建对象
ImmutablePerson person = new ImmutablePerson("Alice", 30);
System.out.println("原始对象: " + person);
// 2. 序列化(对象 → 字节)
byte[] data = serialize(person);
// 3. 反序列化(字节 → 对象)
ImmutablePerson deserialized = (ImmutablePerson) deserialize(data);
System.out.println("反序列化对象: " + deserialized);
System.out.println("对象相等: " + person.equals(deserialized));
}
// --------------------- 序列化方法 ---------------------
// 序列化:对象 → 字节数组
private static byte[] serialize(Object obj) throws IOException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(obj);
return bos.toByteArray();
}
// 反序列化:字节数组 → 对象
private static Object deserialize(byte[] data) throws IOException, ClassNotFoundException {
ByteArrayInputStream bis = new ByteArrayInputStream(data);
ObjectInputStream ois = new ObjectInputStream(bis);
return ois.readObject();
}
// ----------------------------------------------------------------
static class ImmutablePerson implements Serializable {
private static final long serialVersionUID = 1L;
private final String name;
private final int age;
public ImmutablePerson(String name, int age) {
this.name = name;
this.age = age;
}
// ==================== 核心:序列化代理 ====================
// 1. 自动被调用:序列化时,不序列化自己,而是序列化【代理对象】
private Object writeReplace() throws ObjectStreamException {
return new SerializationProxy(this);
}
// 2. 禁止直接反序列化,保护对象安全
private void readObject(ObjectInputStream stream) throws InvalidObjectException {
throw new InvalidObjectException("Use SerializationProxy instead");
}
public String getName() { return name; }
public int getAge() { return age; }
@Override
public String toString() {
return "ImmutablePerson{name='" + name + "', age=" + age + "}";
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ImmutablePerson that = (ImmutablePerson) o;
return age == that.age && Objects.equals(name, that.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
// ==================== 真正被序列化的【代理类】 ====================
// 这个类才是真正被写到字节里的对象
private static class SerializationProxy implements Serializable {
private static final long serialVersionUID = 1L;
private final String name;
private final String age;
SerializationProxy(ImmutablePerson person) {
this.name = person.name;
this.age = person.age;
}
// 反序列化时:用代理对象 → 重建 ImmutablePerson
private Object readResolve() {
return new ImmutablePerson(name, age);
}
}
}
}
|
四、代码示例与最佳实践
4.1 反射工具类
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
|
/**
* 反射工具类
*/
public class ReflectionUtils {
/**
* 创建对象实例
*/
public static <T> T instantiate(Class<T> clazz) {
try {
return clazz.newInstance();
} catch (Exception e) {
throw new RuntimeException("Failed to instantiate class: " + clazz.getName(), e);
}
}
/**
* 调用方法
*/
public static Object invokeMethod(Object obj, String methodName, Object... args) {
try {
Class<?>[] paramTypes = new Class[args.length];
for (int i = 0; i < args.length; i++) {
paramTypes[i] = args[i].getClass();
}
Method method = obj.getClass().getDeclaredMethod(methodName, paramTypes);
method.setAccessible(true);
return method.invoke(obj, args);
} catch (Exception e) {
throw new RuntimeException("Failed to invoke method: " + methodName, e);
}
}
/**
* 获取字段值
*/
public static Object getFieldValue(Object obj, String fieldName) {
try {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
return field.get(obj);
} catch (Exception e) {
throw new RuntimeException("Failed to get field: " + fieldName, e);
}
}
/**
* 设置字段值
*/
public static void setFieldValue(Object obj, String fieldName, Object value) {
try {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
} catch (Exception e) {
throw new RuntimeException("Failed to set field: " + fieldName, e);
}
}
}
|
4.2 序列化工具类
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
|
/**
* 序列化工具类
*/
public class SerializationUtils {
/**
* 序列化对象为字节数组
*/
public static byte[] serialize(Object obj) {
try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos)) {
oos.writeObject(obj);
return bos.toByteArray();
} catch (IOException e) {
throw new RuntimeException("Serialization failed", e);
}
}
/**
* 从字节数组反序列化对象
*/
@SuppressWarnings("unchecked")
public static <T> T deserialize(byte[] data) {
try (ByteArrayInputStream bis = new ByteArrayInputStream(data);
ObjectInputStream ois = new ObjectInputStream(bis)) {
return (T) ois.readObject();
} catch (Exception e) {
throw new RuntimeException("Deserialization failed", e);
}
}
/**
* 深拷贝对象
*/
@SuppressWarnings("unchecked")
public static <T> T deepCopy(T obj) {
if (obj == null) {
return null;
}
return (T) deserialize(serialize(obj));
}
/**
* 序列化对象到文件
*/
public static void serializeToFile(Object obj, String filePath) {
try (ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream(filePath))) {
oos.writeObject(obj);
} catch (IOException e) {
throw new RuntimeException("Failed to serialize to file: " + filePath, e);
}
}
/**
* 从文件反序列化对象
*/
@SuppressWarnings("unchecked")
public static <T> T deserializeFromFile(String filePath) {
try (ObjectInputStream ois = new ObjectInputStream(
new FileInputStream(filePath))) {
return (T) ois.readObject();
} catch (Exception e) {
throw new RuntimeException("Failed to deserialize from file: " + filePath, e);
}
}
}
|
4.3 最佳实践总结
反射最佳实践:
- 优先使用接口和设计模式,避免反射
- 缓存反射对象,提高性能
- 仅在必要时使用
setAccessible(true)
- 处理异常,增强代码健壮性
- 考虑使用 MethodHandle 替代传统反射
序列化最佳实践:
- 始终显式定义
serialVersionUID
- 使用
transient 标记敏感或不需要序列化的字段
- 实现自定义序列化时注意安全
- 考虑使用 JSON/Protobuf 等替代方案
- 对反序列化输入进行验证
五、总结
1 性能优化
- 反射:缓存Class、Method、Field,避免重复获取;少用setAccessible(true),可用MethodHandle提升性能
- 序列化:用transient忽略无用/敏感字段;优先选JSON/Protobuf,高性能场景用Externalizable
2 安全注意
- 反射:谨慎使用setAccessible(true),对输入做校验
- 序列化:不反序列化不可信数据,显式声明serialVersionUID,优先用JSON替代原生序列化
3 最佳实践
- 非必要不使用反射,反射对象必须缓存
- 敏感字段用transient,不可变类用序列化代理模式
- 生产环境优先JSON/Protobuf,完善异常处理
4 应用场景
- 应用:框架底层(Spring/MyBatis)、RPC、动态配置、数据持久化
- 提醒:反射/序列化有性能开销、安全风险,需注意兼容性
反射和序列化是 Java 中强大的特性,掌握它们可以编写更加灵活、动态的代码。但同时也需要注意它们的局限性和潜在风险,在实际应用中权衡利弊,合理使用。