在 Java 中,序列化(Serialization) 是将对象转换为字节流的过程,以便存储到文件、数据库或通过网络传输;反序列化(Deserialization) 则是将字节流还原为对象的过程。Java 通过 java.io.Serializable
接口和相关 API 实现这一机制。以下是其核心原理和实现细节:
1. 实现序列化的条件
-
实现
Serializable
接口:
类必须显式声明实现Serializable
接口(标记接口,无方法)。public class User implements Serializable { private String name; private int age; // 必须有无参构造方法(反序列化时反射调用) public User() {} // getter/setter }
-
serialVersionUID
:
可选字段,用于标识类的版本。若未显式定义,JVM 会根据类结构自动生成,但类结构变化时可能导致反序列化失败。private static final long serialVersionUID = 1L; // 显式定义
2. 序列化与反序列化流程
(1) 序列化(Object → 字节流)
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("user.dat"))) {
User user = new User("Alice", 30);
oos.writeObject(user); // 序列化对象
}
-
底层机制:
-
递归写入对象及其所有引用的非
transient
、非static
字段。 -
每个对象分配唯一 ID,避免重复写入相同对象(通过引用共享)。
-
(2) 反序列化(字节流 → Object)
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("user.dat"))) {
User user = (User) ois.readObject(); // 反序列化
}
-
底层机制:
-
根据字节流重建对象,不调用构造方法(直接分配内存,通过反射设置字段值)。
-
检查
serialVersionUID
:若与当前类版本不匹配,抛出InvalidClassException
。
-
3. 控制序列化行为
(1) transient
关键字
标记字段不参与序列化:
private transient String password; // 不会被序列化
(2) 自定义序列化逻辑
通过重写 writeObject()
和 readObject()
方法:
private void writeObject(ObjectOutputStream oos) throws IOException {
oos.defaultWriteObject(); // 默认序列化
oos.writeUTF(encrypt(password)); // 自定义加密
}
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
ois.defaultReadObject(); // 默认反序列化
this.password = decrypt(ois.readUTF()); // 自定义解密
}
(3) Externalizable
接口
完全控制序列化过程(需实现 writeExternal()
和 readExternal()
):
public class User implements Externalizable {
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeUTF(name);
out.writeInt(age);
}
@Override
public void readExternal(ObjectInput in) throws IOException {
this.name = in.readUTF();
this.age = in.readInt();
}
}
4. 序列化的限制与风险
-
不可序列化的字段:
-
static
字段:属于类,不参与序列化。 -
transient
字段:显式排除。
-
-
安全风险:
-
反序列化可能执行恶意代码(如通过重写
readObject()
)。 -
解决方案:避免反序列化不可信数据,或使用白名单验证(如 Apache Commons IO 的
ValidatingObjectInputStream
)。
-
5. 替代序列化方案
方案 | 特点 |
---|---|
JSON/XML | 跨语言、可读性强(如 Jackson、Gson) |
Protocol Buffers | 高效二进制格式,支持跨语言(Google 出品) |
Kryo | 高性能 Java 序列化库(常用于分布式缓存、RPC) |
Java 原生序列化 | 简单但性能低、安全性差,仅适用于 Java 环境 |
6. 关键注意事项
-
版本兼容性:修改类结构时需谨慎处理
serialVersionUID
。 -
性能优化:避免序列化大对象或深层嵌套结构。
-
敏感数据保护:使用
transient
或自定义加密逻辑。
总结
Java 序列化通过 Serializable
接口和 ObjectInputStream
/ObjectOutputStream
实现,适用于简单场景,但存在性能和安全隐患。实际开发中,推荐优先选择 JSON、Protobuf 等跨语言、高性能的序列化方案。