对象持久化的关键技术
在 Java 开发中,序列化(Serialization) 和 反序列化(Deserialization) 是实现对象持久化、跨系统通信和缓存机制的核心技术。即使你未曾显式使用,它们也广泛存在于 Web 应用、RPC 框架、分布式系统等场景中。
理解序列化与反序列化的定义与作用
掌握 Serializable 接口的使用规则
了解哪些字段会被 / 不会被序列化
熟悉 serialVersionUID 的作用与生成方式
能够编写完整的序列化 / 反序列化示例代码
区分序列化与其他对象复制机制(如克隆)

将 Java 对象的状态 转换为 字节流(byte stream) 的过程,以便存储到文件、数据库或通过网络传输。
将 字节流 重新转换为 内存中的 Java 对象,恢复其原始状态。
关键特性:平台无关性 —— 在 Windows 上序列化的对象,可在 Linux 或 macOS 上成功反序列化。
要让一个类的对象可被序列化,必须实现 java.io.Serializable 接口。
import java.io.Serializable;
class Student implements Serializable {
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
}关于 Serializable 接口
它是一个 标记接口(Marker Interface):没有方法或字段
仅用于“标记”该类允许被序列化
其他标记接口:Cloneable、Remote
如果类未实现
Serializable,调用writeObject()会抛出NotSerializableException。
import java.io.*;
class Demo implements Serializable {
public int a;
public String b;
public Demo(int a, String b) {
this.a = a;
this.b = b;
}
}
public class MainApp {
public static void main(String[] args) {
Demo obj = new Demo(1, "codehub");
String filename = "data.ser";
// 序列化
try (FileOutputStream fos = new FileOutputStream(filename);
ObjectOutputStream oos = new ObjectOutputStream(fos)) {
oos.writeObject(obj);
System.out.println("对象已序列化");
} catch (IOException e) {
System.err.println("序列化失败: " + e.getMessage());
}
// 反序列化
try (FileInputStream fis = new FileInputStream(filename);
ObjectInputStream ois = new ObjectInputStream(fis)) {
Demo restored = (Demo) ois.readObject();
System.out.println("对象已反序列化");
System.out.println("a = " + restored.a); // 输出:1
System.out.println("b = " + restored.b); // 输出:codehub
} catch (IOException | ClassNotFoundException e) {
System.err.println("反序列化失败: " + e.getMessage());
}
}
}使用 try-with-resources 自动关闭流,避免资源泄漏。
瞬态变量与静态变量的行为
class Emp implements Serializable {
private static final long serialVersionUID = 1L;
transient int secret; // 不会被序列化
static int counter = 100; // 不会被序列化
String name;
int age;
public Emp(String name, int age, int secret) {
this.name = name;
this.age = age;
this.secret = secret;
counter++; // 静态变量,所有实例共享
}
}
public class TestTransient {
public static void main(String[] args) throws Exception {
Emp e1 = new Emp("张三", 25, 999);
System.out.println("序列化前: secret=" + e1.secret + ", counter=" + Emp.counter);
// 序列化
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("emp.ser"))) {
oos.writeObject(e1);
}
// 修改静态变量
Emp.counter = 2000;
// 反序列化
Emp e2;
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("emp.ser"))) {
e2 = (Emp) ois.readObject();
}
System.out.println("反序列化后: secret=" + e2.secret + ", counter=" + Emp.counter);
}
}输出:
序列化前: secret=999, counter=101
反序列化后: secret=0, counter=2000解释:
secret是transient,反序列化后为默认值0
counter是static,反序列化时不恢复,而是使用当前类中的值(2000)
serialVersionUID版本控制的关键
当类结构发生变化(如新增字段),若未显式声明 serialVersionUID,JVM 会自动生成一个基于类结构的 UID。一旦类变更,UID 改变,反序列化将失败(抛出 InvalidClassException)。
private static final long serialVersionUID = 1L;必须是 static final long 类型
建议使用 private 修饰(不被继承)
值可任意指定(通常用 1L 或时间戳)
serialver 工具生成 UID注意:也可以使用插件生成
JDK 自带工具,可查看已有类的 UID:
serialver com.example.MyClass最佳实践:所有可序列化类都应显式声明
serialVersionUID,避免因编译器差异导致兼容性问题。
反序列化时,不会调用任何构造函数,包括无参构造函数。对象是通过反射直接分配内存并填充字段的。
若父类实现了 Serializable,子类自动可序列化
若子类可序列化但父类不可,则父类必须有无参构造函数(用于初始化非序列化部分)
如果对象包含其他对象引用(如 List<User>),这些关联对象也必须实现 Serializable,否则会抛出异常。
transient 与 final 的特殊组合即使将 final 字段标记为 transient,它仍会被序列化!因为 final 字段在编译期常被内联(inline),其值直接嵌入字节码。
示例验证
class Dog implements Serializable {
int i = 10;
transient final int j = 20; // 看似不会序列化,实际会!
}
public class FinalTransientTest {
public static void main(String[] args) throws Exception {
Dog d1 = new Dog();
// 序列化
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("dog.ser"))) {
oos.writeObject(d1);
}
// 反序列化
Dog d2;
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("dog.ser"))) {
d2 = (Dog) ois.readObject();
}
System.out.println(d2.i + "\t" + d2.j); // 输出:10 20
}
}结论:
final字段无论是否transient,都会被序列化(因其值在编译时已确定)。
序列化 = 对象 → 字节流;反序列化 = 字节流 → 对象
类必须实现 Serializable 接口才能被序列化
static 和 transient 字段 不会被序列化
务必显式声明 serialVersionUID 以保证版本兼容性
反序列化时 不调用构造函数
关联对象也必须可序列化,否则失败
final 字段即使标记 transient 也会被序列化(因编译期内联)
如果一个类实现了 Serializable,但它的某个字段是不可序列化的第三方类对象,该如何处理?
为什么反序列化时不调用构造函数?这对对象初始化有何影响?
在微服务架构中,序列化常用于哪些场景?除了 Java 原生序列化,还有哪些更高效的替代方案(如 JSON、Protobuf)?