【Java】Java 序列化详解:Serializable、transient 与 Externalizable 全面解析
大家好,今天继续分享 Java 核心知识点,本次重点讲解对象序列化相关内容,包括 Serializable 接口、transient 关键字、Externalizable 接口以及序列化版本号 serialVersionUID,同时搭配实战代码,帮大家彻底吃透 Java 序列化。
一、什么是 Java 序列化与反序列化
Java 序列化从 JDK 1.1 版本开始引入,简单来说:
- 序列化:把 Java 对象转换成字节序列,方便对象持久化到本地文件、或是在网络中传输。
- 反序列化:将字节序列还原为原始的 Java 对象。
如果一个类想要实现序列化,必须实现 Serializable 接口,否则程序运行时会直接抛出 NotSerializableException 异常。
值得一提的是,Serializable 是一个空标识接口,内部没有任何抽象方法,它的唯一作用就是告知 JVM:当前类的对象支持序列化操作。
// Serializable 接口源码
public interface Serializable {
}
二、基础序列化实战(Serializable 用法)
下面通过完整代码演示对象序列化与反序列化的基础使用。
1. 实体类(实现 Serializable 接口)
import java.io.Serializable;
/**
* 学生实体类,实现序列化接口
*/
public class Student implements Serializable {
// 成员变量
private String name;
private int age;
// 构造方法
public Student(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;
}
// 重写toString,方便打印对象信息
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
2. 序列化 & 反序列化测试类
借助 ObjectOutputStream 完成序列化,ObjectInputStream 完成反序列化:
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class SerializeTest {
public static void main(String[] args) {
// 1. 序列化:对象写入文件
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("student.txt"))) {
Student stu = new Student("云扬", 22);
oos.writeObject(stu);
System.out.println("序列化完成!");
} catch (Exception e) {
e.printStackTrace();
}
// 2. 反序列化:从文件读取字节,还原对象
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("student.txt"))) {
Student student = (Student) ois.readObject();
System.out.println("反序列化结果:" + student);
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行结果:
序列化完成!
反序列化结果:Student{name='云扬', age=22}
整个过程由 JVM 自动处理字段的读写,这也是 Serializable 接口的核心特点。
三、transient 关键字:屏蔽字段序列化
在实际开发中,有些敏感字段(如密码、临时变量)不希望被序列化持久化,这时就可以使用 transient 关键字。
核心规则
transient仅能修饰成员变量,不能修饰类、方法;- 被
transient修饰的字段,不会参与序列化; - 反序列化后,该字段会被赋予数据类型默认值(引用类型
null、数值类型 0、布尔类型 false); static修饰的静态变量本身就不属于对象,无论是否加transient,都不会被序列化。
代码示例
修改 Student 类,给年龄字段加上 transient:
import java.io.Serializable;
public class Student implements Serializable {
private String name;
// 被transient修饰,禁止序列化
private transient int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
// getter、setter、toString 省略
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
再次运行测试类,输出结果:
序列化完成!
反序列化结果:Student{name='云扬', age=0}
可以看到 age 字段丢失,变成了 int 类型默认值 0,符合 transient 的特性。
四、Externalizable:手动控制序列化
Java 除了 Serializable,还提供了 Externalizable 接口,它继承自 Serializable,可以让开发者手动定义序列化、反序列化规则,粒度控制更强。
核心特点
- 必须重写
writeExternal()和readExternal()两个方法,手动指定需要序列化的字段; - 强制要求类提供无参构造方法,反序列化时会先调用无参构造创建对象;
transient关键字对它失效,序列化规则完全由开发者手写;- 相比自动序列化,性能更高,但编码成本更大。
代码实战
1. 实现 Externalizable 接口的实体类
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
public class User implements Externalizable {
private String username;
private String password;
// 必须提供无参构造方法
public User() {
System.out.println("无参构造方法被调用(反序列化时执行)");
}
public User(String username, String password) {
this.username = username;
this.password = password;
}
// 手动序列化:指定要写出的字段
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(username);
// 故意不写入password,实现敏感字段屏蔽
}
// 手动反序列化:读取字段
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
this.username = (String) in.readObject();
}
@Override
public String toString() {
return "User{" +
"username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
}
2. 测试类
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class ExternalizableTest {
public static void main(String[] args) {
// 序列化
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("user.txt"))) {
User user = new User("admin", "123456");
oos.writeObject(user);
System.out.println("User 对象序列化完成");
} catch (Exception e) {
e.printStackTrace();
}
// 反序列化
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("user.txt"))) {
User user = (User) ois.readObject();
System.out.println("反序列化结果:" + user);
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行输出:
User 对象序列化完成
无参构造方法被调用(反序列化时执行)
反序列化结果:User{username='admin', password='null'}
我们手动只序列化了用户名,密码字段直接丢失,同时也验证了反序列化会调用无参构造。
Serializable 与 Externalizable 对比总结
| 特性 | Serializable | Externalizable |
|---|---|---|
| 方法实现 | 空接口,无需重写方法 | 必须重写 writeExternal /readExternal |
| 无参构造 | 不要求 | 强制要求 |
| 字段控制 | 自动序列化,transient 生效 | 手动控制字段,transient 失效 |
| 性能 | 较低(JVM 自动反射处理) | 较高(手动读写) |
| 适用场景 | 简单对象、快速开发 | 敏感数据加密、高性能场景 |
五、serialVersionUID:序列化版本号
这是序列化中极易踩坑的知识点。serialVersionUID 是序列化版本标识符,作用是保证类版本升级后,旧的字节流依旧可以正常反序列化。
工作原理
反序列化时,JVM 会对比字节流中的版本号和当前类的版本号:
- 版本号一致:正常反序列化;
- 版本号不一致:抛出
InvalidClassException版本不匹配异常。
版本号三种写法
- 手动固定版本号(推荐)
private static final long serialVersionUID = 1L;
类结构小幅修改(新增普通字段)时,版本号不变,兼容旧数据。
- IDE 自动生成随机版本号
private static final long serialVersionUID = -2095916884810199532L;
类一旦修改,建议同步更新版本号,适用于版本严格隔离的场景。
- 使用注解抑制警告
@SuppressWarnings("serial")
踩坑演示
如果不手动定义 serialVersionUID,JVM 会根据类结构自动生成版本号。一旦修改类(新增 / 删除字段、修改方法),自动版本号就会变更,旧文件将无法反序列化。
最佳实践:所有实现 Serializable 的类,都手动声明 serialVersionUID = 1L,最大程度兼容版本迭代。
六、整体总结
- 序列化核心:实现
Serializable空接口,依靠ObjectOutputStream/ObjectInputStream完成读写; - transient 用于临时屏蔽对象字段,静态变量天然不参与序列化;
- Externalizable 适合精细化控制序列化逻辑,必须实现两个抽象方法 + 无参构造;
- serialVersionUID 是版本兼容的关键,开发中务必手动定义,避免线上版本报错。
Java 序列化在 IO 流、网络编程、缓存框架中使用十分广泛,理解底层规则和边界问题,才能在开发中避开各类异常。



