Java 序列化是将对象的状态转换为字节流的过程,以便可以将其存储在文件、数据库中,或通过网络传输;而反序列化则是将字节流恢复为对象的过程。这一机制在分布式系统、缓存、持久化等场景中广泛应用。
一、序列化的基本概念
- 序列化(Serialization):把对象转换为字节序列的过程。
- 反序列化(Deserialization):把字节序列恢复为对象的过程。
- 核心用途:对象的持久化存储、跨网络传输对象。
二、实现序列化的条件
要让一个类可序列化,需满足以下条件:
- 类必须实现
java.io.Serializable接口(这是一个标记接口,无任何方法)。 - 类的所有非静态成员变量必须是可序列化的(如果有不可序列化的成员,需用
transient关键字修饰)。 - 若类有父类,父类要么可序列化,要么有默认无参构造方法(否则反序列化会报错)。
三、核心类与方法
Java 提供了 ObjectOutputStream 和 ObjectInputStream 来处理序列化和反序列化:
-
ObjectOutputStream:
- 用于序列化对象,核心方法:
writeObject(Object obj) // 将对象写入输出流
- 用于序列化对象,核心方法:
-
ObjectInputStream:
- 用于反序列化对象,核心方法:
readObject() // 从输入流读取对象并返回
- 用于反序列化对象,核心方法:
四、基本使用示例
import java.io.*;
// 可序列化的类
class Person implements Serializable {
private String name;
private int age;
// transient 修饰的变量不会被序列化
private transient String password;
public Person(String name, int age, String password) {
this.name = name;
this.age = age;
this.password = password;
}
@Override
public String toString() {
return "Person{name='" + name + "', age=" + age + ", password='" + password + "'}";
}
}
public class SerializationDemo {
public static void main(String[] args) {
// 序列化
try (ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("person.ser"))) {
Person person = new Person("Alice", 30, "123456");
oos.writeObject(person);
System.out.println("序列化完成");
} catch (IOException e) {
e.printStackTrace();
}
// 反序列化
try (ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("person.ser"))) {
Person deserializedPerson = (Person) ois.readObject();
System.out.println("反序列化结果: " + deserializedPerson);
// 输出:Person{name='Alice', age=30, password='null'}
// 可见 password 因 transient 未被序列化
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
五、serialVersionUID 的作用
serialVersionUID 是一个静态常量,用于标识类的版本。其作用是:
- 序列化时,JVM 会将类的
serialVersionUID写入字节流。 - 反序列化时,JVM 会检查字节流中的
serialVersionUID与当前类的是否一致:- 一致:正常反序列化。
- 不一致:抛出
InvalidClassException。
建议显式声明:
class Person implements Serializable {
// 显式声明 serialVersionUID
private static final long serialVersionUID = 1L;
// ... 其他成员
}
若不声明,JVM 会根据类的结构(成员变量、方法等)自动生成,但类结构的微小变化(如增加字段)都会导致 serialVersionUID 变化,从而破坏兼容性。
六、transient 关键字
- 被
transient修饰的成员变量不会被序列化,反序列化时会被赋予默认值(如null、0)。 - 常用于敏感信息(如密码)或不需要持久化的临时变量。
七、自定义序列化与反序列化
通过重写以下方法,可自定义序列化 / 反序列化逻辑:
// 自定义序列化逻辑
private void writeObject(ObjectOutputStream out) throws IOException {
// 先执行默认序列化
out.defaultWriteObject();
// 自定义处理(如加密敏感字段)
out.writeObject(encrypt(password));
}
// 自定义反序列化逻辑
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
// 先执行默认反序列化
in.defaultReadObject();
// 自定义处理(如解密)
password = decrypt((String) in.readObject());
}
// 示例加密方法(简化)
private String encrypt(String str) {
return str + "_encrypted";
}
private String decrypt(String str) {
return str.replace("_encrypted", "");
}
八、单例模式与序列化
默认情况下,序列化会破坏单例(反序列化会创建新对象)。解决方法:重写 readResolve() 方法:
class Singleton implements Serializable {
private static final long serialVersionUID = 1L;
private static Singleton instance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
// 确保反序列化返回唯一实例
private Object readResolve() {
return instance;
}
}
九、序列化的限制与注意事项
-
不可序列化的类型:
- 某些原生类型(如
Thread、Socket)不可序列化。 - 被
transient修饰的成员不可序列化。
- 某些原生类型(如
-
安全性问题:
- 反序列化可能存在安全风险(如恶意构造字节流执行恶意代码),需谨慎处理不可信数据。
- Java 9+ 引入了序列化过滤机制(
ObjectInputFilter)来增强安全性。
-
性能影响:
- 序列化会产生额外的字节流开销,频繁序列化可能影响性能。
- 可考虑更高效的序列化框架(如 Protobuf、Kryo)替代原生序列化。
-
版本兼容性:
- 类结构变化需谨慎,建议通过
serialVersionUID控制版本。 - 新增字段时,反序列化旧版本数据会使用默认值;删除字段时,旧版本数据中的对应字段会被忽略。
- 类结构变化需谨慎,建议通过
十、常见异常
NotSerializableException:类未实现Serializable接口。InvalidClassException:serialVersionUID不匹配或类结构不兼容。ClassNotFoundException:反序列化时找不到对应的类。
总结
Java 序列化是对象持久化和网络传输的基础机制,通过实现 Serializable 接口、合理使用 serialVersionUID 和 transient 关键字,以及自定义序列化逻辑,可以灵活应对各种场景。但需注意其安全性和性能问题,在分布式系统中可考虑更高效的序列化方案。
497

被折叠的 条评论
为什么被折叠?



