java中的Serializable

Posted by     "zengchengjie" on Tuesday, January 7, 2020

Java 中的 Serializable:深入理解序列化机制

引言

在分布式系统盛行的今天,数据需要在不同的 JVM 之间传输,对象需要在内存和磁盘之间持久化。这引出了一个核心问题:如何将一个 Java 对象从内存中“冷冻”起来,需要时再“解冻”恢复?

答案就是序列化(Serialization)。Java 提供了 Serializable 接口,它像一个“魔法开关”——只要一个类实现了这个接口,它的对象就可以自动转换为字节流,用于网络传输、文件存储或深度克隆。

这个机制看似简单,背后却隐藏着丰富的细节。本文将带你深入理解 Java 序列化,从基础使用到高级特性,从常见陷阱到最佳实践,让你完全掌握这项重要技能。

一、序列化是什么?

1.1 定义与用途

序列化:将 Java 对象转换为字节序列的过程。
反序列化:将字节序列恢复为 Java 对象的过程。

┌─────────────┐   serialize    ┌─────────────┐   deserialize   ┌─────────────┐
│   Java对象   │ ─────────────► │   字节流     │ ──────────────► │   Java对象   │
│  (内存中)    │                │  (文件/网络)  │                 │  (内存中)    │
└─────────────┘                └─────────────┘                 └─────────────┘

主要用途

场景 说明
持久化存储 将对象保存到文件或数据库
网络传输 RMI、Dubbo、gRPC 等 RPC 框架
分布式缓存 Redis、Memcached 存储对象
深度克隆 通过序列化实现深拷贝
Session 管理 Tomcat 等 Web 容器的 Session 钝化

1.2 Serializable 接口

Serializable 是一个标记接口(Marker Interface),没有任何方法需要实现:

public interface Serializable {
    // 空的标记接口
}

只要一个类实现了 Serializable,它的对象就可以被序列化:

import java.io.Serializable;

public class User implements Serializable {
    private String name;
    private int age;
    private String email;
    
    // 构造方法、getter、setter...
}

二、基础使用

2.1 简单示例

import java.io.*;

public class BasicSerialization {
    
    static class Person implements Serializable {
        private static final long serialVersionUID = 1L;
        private String name;
        private int age;
        
        public Person(String name, int age) {
            this.name = name;
            this.age = age;
        }
        
        @Override
        public String toString() {
            return "Person{name='" + name + "', age=" + age + "}";
        }
    }
    
    public static void main(String[] args) throws Exception {
        Person person = new Person("张三", 25);
        
        // 序列化:对象 → 文件
        try (ObjectOutputStream oos = new ObjectOutputStream(
                new FileOutputStream("person.ser"))) {
            oos.writeObject(person);
            System.out.println("序列化完成");
        }
        
        // 反序列化:文件 → 对象
        try (ObjectInputStream ois = new ObjectInputStream(
                new FileInputStream("person.ser"))) {
            Person deserialized = (Person) ois.readObject();
            System.out.println("反序列化结果: " + deserialized);
        }
    }
}

2.2 核心 API

作用
ObjectOutputStream 对象输出流,负责序列化
ObjectInputStream 对象输入流,负责反序列化
Serializable 标记接口,表示可序列化
Externalizable 自定义序列化接口
serialVersionUID 版本控制标识

2.3 复杂对象序列化

public class ComplexSerialization {
    
    static class Address implements Serializable {
        private String city;
        private String street;
        
        public Address(String city, String street) {
            this.city = city;
            this.street = street;
        }
        
        @Override
        public String toString() {
            return city + " " + street;
        }
    }
    
    static class Employee implements Serializable {
        private static final long serialVersionUID = 1L;
        private String name;
        private int salary;
        private Address address;      // 引用类型
        private String[] skills;      // 数组
        private List<String> projects; // 集合
        
        public Employee(String name, int salary, Address address, 
                        String[] skills, List<String> projects) {
            this.name = name;
            this.salary = salary;
            this.address = address;
            this.skills = skills;
            this.projects = projects;
        }
        
        @Override
        public String toString() {
            return String.format("Employee{name='%s', salary=%d, address=%s, skills=%s, projects=%s}",
                name, salary, address, Arrays.toString(skills), projects);
        }
    }
    
    public static void main(String[] args) throws Exception {
        Address addr = new Address("北京", "朝阳路1号");
        String[] skills = {"Java", "Python", "Go"};
        List<String> projects = Arrays.asList("电商系统", "支付系统");
        
        Employee emp = new Employee("李四", 30000, addr, skills, projects);
        
        // 序列化
        byte[] data;
        try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
             ObjectOutputStream oos = new ObjectOutputStream(baos)) {
            oos.writeObject(emp);
            data = baos.toByteArray();
            System.out.println("序列化字节长度: " + data.length);
        }
        
        // 反序列化
        try (ByteArrayInputStream bais = new ByteArrayInputStream(data);
             ObjectInputStream ois = new ObjectInputStream(bais)) {
            Employee recovered = (Employee) ois.readObject();
            System.out.println("反序列化结果: " + recovered);
        }
    }
}

三、serialVersionUID:版本控制的核心

3.1 为什么需要 serialVersionUID

当序列化和反序列化的类版本不一致时,JVM 会抛出 InvalidClassExceptionserialVersionUID 就是用来验证版本一致性的。

// 场景演示:类版本变更导致的问题
public class VersionDemo {
    
    // 版本1:只有 name 字段
    static class UserV1 implements Serializable {
        private String name;
        // 没有显式声明 serialVersionUID
    }
    
    // 版本2:增加了 age 字段
    static class UserV2 implements Serializable {
        private String name;
        private int age;  // 新增字段
    }
    
    public static void main(String[] args) throws Exception {
        // 用 V1 序列化
        UserV1 v1 = new UserV1();
        // 假设序列化后保存到文件...
        
        // 用 V2 反序列化会失败!
        // 因为 JVM 自动生成的 serialVersionUID 不同
    }
}

3.2 显式声明 serialVersionUID

最佳实践:始终显式声明 serialVersionUID

public class User implements Serializable {
    // 显式声明,固定版本号
    private static final long serialVersionUID = 1L;
    
    private String name;
    private int age;
    private String email;
}

// 版本升级时,可以增加版本号
public class User implements Serializable {
    private static final long serialVersionUID = 2L;  // 版本升级
    
    private String name;
    private int age;
    private String email;
    private String phone;  // 新增字段
}

3.3 serialVersionUID 的生成规则

如果没有显式声明,JVM 会根据类的结构计算一个 hash 值:

  • 类名
  • 字段(名称、类型、修饰符)
  • 方法(名称、返回类型、参数)
  • 实现的接口

任何变化(哪怕是添加一个空格注释)都会改变自动生成的 UID。

3.4 兼容性规则

变更类型 兼容性
添加字段 兼容(反序列化时使用默认值)
删除字段 兼容(字段值被忽略)
添加方法 兼容
修改字段类型 不兼容
修改类名 不兼容
修改继承关系 不兼容
字段改为 transient 兼容(值被忽略)

四、transient:跳过敏感字段

4.1 基本用法

有些字段不应该被序列化,比如密码、密钥、数据库连接等。使用 transient 关键字标记:

public class UserAccount implements Serializable {
    private static final long serialVersionUID = 1L;
    
    private String username;
    private transient String password;  // 不会被序列化
    private transient Connection dbConnection;  // 连接对象不应序列化
    
    private int loginCount;
    
    public UserAccount(String username, String password) {
        this.username = username;
        this.password = password;
        this.loginCount = 0;
    }
    
    @Override
    public String toString() {
        return String.format("User{username='%s', password='%s', loginCount=%d}",
            username, password, loginCount);
    }
}

// 测试
public class TransientDemo {
    public static void main(String[] args) throws Exception {
        UserAccount account = new UserAccount("alice", "secret123");
        
        // 序列化
        byte[] data;
        try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
             ObjectOutputStream oos = new ObjectOutputStream(baos)) {
            oos.writeObject(account);
            data = baos.toByteArray();
        }
        
        // 反序列化
        try (ByteArrayInputStream bais = new ByteArrayInputStream(data);
             ObjectInputStream ois = new ObjectInputStream(bais)) {
            UserAccount recovered = (UserAccount) ois.readObject();
            System.out.println(recovered);
            // 输出: User{username='alice', password='null', loginCount=0}
            // password 字段为 null(transient 字段被跳过)
        }
    }
}

4.2 transient 的使用场景

public class CachedData implements Serializable {
    private static final long serialVersionUID = 1L;
    
    private String id;
    private String data;
    
    // 缓存数据,不需要序列化
    private transient Cache<String, String> cache;
    
    // 日志记录器,不需要序列化
    private transient Logger logger = LoggerFactory.getLogger(CachedData.class);
    
    // 敏感信息
    private transient String apiKey;
    
    // 运行时状态
    private transient AtomicInteger accessCount = new AtomicInteger(0);
    
    // 自定义反序列化逻辑,重建 transient 字段
    private void readObject(ObjectInputStream ois) 
            throws IOException, ClassNotFoundException {
        ois.defaultReadObject();
        // 重建 transient 字段
        this.cache = new LRUCache<>();
        this.logger = LoggerFactory.getLogger(CachedData.class);
        this.accessCount = new AtomicInteger(0);
    }
}

五、自定义序列化

5.1 实现 writeObject/readObject

通过实现私有方法 writeObjectreadObject,可以精确控制序列化过程:

public class CustomSerialization implements Serializable {
    private static final long serialVersionUID = 1L;
    
    private String name;
    private int age;
    private transient String encryptedPassword;
    private String secretKey;
    
    public CustomSerialization(String name, int age, String password) {
        this.name = name;
        this.age = age;
        this.encryptedPassword = encrypt(password);
        this.secretKey = generateKey();
    }
    
    // 自定义序列化
    private void writeObject(ObjectOutputStream oos) throws IOException {
        // 执行默认序列化(非 transient 字段)
        oos.defaultWriteObject();
        
        // 自定义处理:加密敏感数据后写入
        String encrypted = encrypt(encryptedPassword);
        oos.writeUTF(encrypted);
        
        System.out.println("自定义序列化完成");
    }
    
    // 自定义反序列化
    private void readObject(ObjectInputStream ois) 
            throws IOException, ClassNotFoundException {
        // 执行默认反序列化
        ois.defaultReadObject();
        
        // 读取并解密敏感数据
        String encrypted = ois.readUTF();
        this.encryptedPassword = decrypt(encrypted);
        
        System.out.println("自定义反序列化完成");
    }
    
    private String encrypt(String s) {
        // 加密逻辑(简化)
        return new StringBuilder(s).reverse().toString();
    }
    
    private String decrypt(String s) {
        // 解密逻辑
        return new StringBuilder(s).reverse().toString();
    }
    
    private String generateKey() {
        return "secret-key-" + System.currentTimeMillis();
    }
    
    @Override
    public String toString() {
        return String.format("Custom{name='%s', age=%d, password='%s', key='%s'}",
            name, age, encryptedPassword, secretKey);
    }
}

5.2 使用 Externalizable 接口

Externalizable 提供了完全的控制权,但需要手动处理所有字段:

public class ExternalizableDemo implements Externalizable {
    private String name;
    private int age;
    private String email;
    
    // 必须提供无参构造器
    public ExternalizableDemo() {}
    
    public ExternalizableDemo(String name, int age, String email) {
        this.name = name;
        this.age = age;
        this.email = email;
    }
    
    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        // 手动写入字段(只有选择的部分)
        out.writeUTF(name);
        out.writeInt(age);
        // email 故意不写入
        System.out.println("writeExternal 执行");
    }
    
    @Override
    public void readExternal(ObjectInput in) throws IOException {
        // 手动读取,顺序必须与写入一致
        this.name = in.readUTF();
        this.age = in.readInt();
        this.email = "default@example.com";  // 设置默认值
        System.out.println("readExternal 执行");
    }
    
    @Override
    public String toString() {
        return String.format("Externalizable{name='%s', age=%d, email='%s'}",
            name, age, email);
    }
}

Serializable vs Externalizable

特性 Serializable Externalizable
实现难度 简单 复杂
控制粒度 自动 完全手动
性能 较慢(反射) 较快
灵活性 有限 极高
推荐程度 优先使用 性能敏感时使用

5.3 writeReplace 和 readResolve

这两个方法用于在序列化/反序列化过程中替换对象:

public class SingletonDemo implements Serializable {
    private static final long serialVersionUID = 1L;
    
    // 单例实例
    private static final SingletonDemo INSTANCE = new SingletonDemo();
    
    private SingletonDemo() {}
    
    public static SingletonDemo getInstance() {
        return INSTANCE;
    }
    
    // 序列化时返回替代对象
    private Object writeReplace() throws ObjectStreamException {
        System.out.println("writeReplace 执行");
        return new SerializationProxy();
    }
    
    // 反序列化时返回替代对象(防止破坏单例)
    private Object readResolve() throws ObjectStreamException {
        System.out.println("readResolve 执行");
        return INSTANCE;
    }
    
    // 代理类
    private static class SerializationProxy implements Serializable {
        private static final long serialVersionUID = 1L;
        
        private Object readResolve() throws ObjectStreamException {
            return INSTANCE;
        }
    }
}

六、继承与序列化

6.1 父类实现 Serializable

如果父类实现了 Serializable,子类自动可序列化:

class Parent implements Serializable {
    private static final long serialVersionUID = 1L;
    protected String parentField;
}

class Child extends Parent {
    private String childField;
    // 自动可序列化
}

6.2 父类未实现 Serializable

如果父类没有实现 Serializable,反序列化时会调用父类的无参构造器

class NonSerializableParent {
    private String parentField;
    
    public NonSerializableParent() {
        this.parentField = "默认值";
        System.out.println("父类无参构造器被调用");
    }
    
    public NonSerializableParent(String value) {
        this.parentField = value;
    }
    
    public String getParentField() { return parentField; }
    public void setParentField(String value) { parentField = value; }
}

class Child extends NonSerializableParent implements Serializable {
    private static final long serialVersionUID = 1L;
    private String childField;
    
    public Child(String parentValue, String childValue) {
        super(parentValue);
        this.childField = childValue;
    }
    
    // 手动处理父类字段
    private void writeObject(ObjectOutputStream oos) throws IOException {
        oos.defaultWriteObject();
        oos.writeUTF(getParentField());
    }
    
    private void readObject(ObjectInputStream ois) 
            throws IOException, ClassNotFoundException {
        ois.defaultReadObject();
        setParentField(ois.readUTF());
    }
}

七、常见问题与最佳实践

7.1 哪些对象不能序列化?

// ❌ 以下类型不能被序列化
// 1. Thread 对象(与特定 JVM 状态相关)
// 2. Socket、Stream 等 I/O 对象
// 3. 没有实现 Serializable 的自定义类
// 4. 静态字段(静态变量属于类,不属于对象)
// 5. transient 字段

// ✅ 解决方法:标记为 transient
public class MyService implements Serializable {
    private transient Thread workerThread;
    private transient Socket connection;
    private transient InputStream inputStream;
    
    private static final long serialVersionUID = 1L;
}

7.2 序列化性能优化

// 1. 使用缓冲区
try (BufferedOutputStream bos = new BufferedOutputStream(
         new FileOutputStream("data.ser"));
     ObjectOutputStream oos = new ObjectOutputStream(bos)) {
    oos.writeObject(obj);
}

// 2. 使用 Externalizable 替代 Serializable
public class FastSerializable implements Externalizable {
    // 手动序列化,性能更好
}

// 3. 压缩序列化数据
public static byte[] compressAndSerialize(Object obj) throws IOException {
    try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
         GZIPOutputStream gzip = new GZIPOutputStream(baos);
         ObjectOutputStream oos = new ObjectOutputStream(gzip)) {
        oos.writeObject(obj);
        oos.flush();
        return baos.toByteArray();
    }
}

7.3 安全考虑

// 1. 防止序列化攻击
public class SecureObject implements Serializable {
    private static final long serialVersionUID = 1L;
    
    private String sensitiveData;
    
    // 验证反序列化的数据
    private void readObject(ObjectInputStream ois) 
            throws IOException, ClassNotFoundException {
        ois.defaultReadObject();
        
        // 验证数据合法性
        if (sensitiveData == null || sensitiveData.isEmpty()) {
            throw new InvalidObjectException("敏感数据不能为空");
        }
        
        // 防止恶意注入
        if (sensitiveData.contains("<script>")) {
            throw new InvalidObjectException("包含非法字符");
        }
    }
    
    // 防止通过序列化创建额外的实例
    private Object readResolve() throws ObjectStreamException {
        return getInstance();  // 返回单例
    }
}

// 2. 自定义 ObjectInputStream 过滤类
public class SafeObjectInputStream extends ObjectInputStream {
    private static final Set<String> ALLOWED_CLASSES = Set.of(
        "com.example.User",
        "com.example.Address"
    );
    
    public SafeObjectInputStream(InputStream in) throws IOException {
        super(in);
    }
    
    @Override
    protected Class<?> resolveClass(ObjectStreamClass desc) 
            throws IOException, ClassNotFoundException {
        String className = desc.getName();
        if (!ALLOWED_CLASSES.contains(className)) {
            throw new InvalidClassException("不允许的类: " + className);
        }
        return super.resolveClass(desc);
    }
}

7.4 版本演进最佳实践

public class EvolvingClass implements Serializable {
    private static final long serialVersionUID = 1L;
    
    // 原始版本字段
    private String name;
    private int age;
    
    // 新增字段(向后兼容)
    private String email;           // 新增
    private transient String phone; // 新增,但不序列化
    
    // 删除字段(标记为 transient,兼容旧数据)
    private transient String oldField;  // 旧版本字段
    
    // 使用 optional data 处理版本差异
    private void readObject(ObjectInputStream ois) 
            throws IOException, ClassNotFoundException {
        ois.defaultReadObject();
        
        // 尝试读取可选数据(新版本可能写入的额外数据)
        try {
            String extra = ois.readUTF();
            // 处理额外数据...
        } catch (EOFException e) {
            // 旧版本没有写入额外数据
        }
    }
    
    private void writeObject(ObjectOutputStream oos) throws IOException {
        oos.defaultWriteObject();
        // 写入版本信息
        oos.writeUTF("v2");
        // 写入额外数据
        oos.writeUTF("additional-data");
    }
}

八、序列化与框架

8.1 常见框架的序列化机制

框架 序列化方式 特点
Java 原生 Serializable 方便,但性能一般
Hessian 二进制 跨语言,性能较好
Kryo 二进制 高性能,需注册类
Protobuf 二进制 跨语言,高性能,需要 .proto 文件
JSON 文本 可读性强,性能较差
XML 文本 可读性强,冗余多

8.2 性能对比示例

// 使用 Kryo 替代原生序列化(性能提升 5-10 倍)
public class KryoExample {
    private static final Kryo kryo = new Kryo();
    
    static {
        kryo.register(User.class);
        kryo.setRegistrationRequired(true);  // 要求注册,性能更好
    }
    
    public static byte[] serialize(Object obj) {
        try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
             Output output = new Output(baos)) {
            kryo.writeObject(output, obj);
            output.flush();
            return baos.toByteArray();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    
    public static <T> T deserialize(byte[] data, Class<T> type) {
        try (ByteArrayInputStream bais = new ByteArrayInputStream(data);
             Input input = new Input(bais)) {
            return kryo.readObject(input, type);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

九、完整实战:序列化工具类

import java.io.*;
import java.util.Base64;

public class SerializationUtils {
    
    private static final int BUFFER_SIZE = 8192;
    
    /**
     * 序列化对象为字节数组
     */
    public static byte[] serialize(Object obj) {
        if (obj == null) {
            return null;
        }
        
        try (ByteArrayOutputStream baos = new ByteArrayOutputStream(BUFFER_SIZE);
             ObjectOutputStream oos = new ObjectOutputStream(baos)) {
            oos.writeObject(obj);
            oos.flush();
            return baos.toByteArray();
        } catch (IOException e) {
            throw new RuntimeException("序列化失败: " + e.getMessage(), e);
        }
    }
    
    /**
     * 反序列化字节数组为对象
     */
    @SuppressWarnings("unchecked")
    public static <T> T deserialize(byte[] data) {
        if (data == null || data.length == 0) {
            return null;
        }
        
        try (ByteArrayInputStream bais = new ByteArrayInputStream(data);
             ObjectInputStream ois = new ObjectInputStream(bais)) {
            return (T) ois.readObject();
        } catch (IOException | ClassNotFoundException e) {
            throw new RuntimeException("反序列化失败: " + e.getMessage(), e);
        }
    }
    
    /**
     * 序列化为 Base64 字符串(便于文本传输)
     */
    public static String serializeToBase64(Object obj) {
        byte[] data = serialize(obj);
        return data == null ? null : Base64.getEncoder().encodeToString(data);
    }
    
    /**
     * 从 Base64 字符串反序列化
     */
    public static <T> T deserializeFromBase64(String base64) {
        if (base64 == null || base64.isEmpty()) {
            return null;
        }
        byte[] data = Base64.getDecoder().decode(base64);
        return deserialize(data);
    }
    
    /**
     * 序列化到文件
     */
    public static void serializeToFile(Object obj, String filePath) throws IOException {
        try (FileOutputStream fos = new FileOutputStream(filePath);
             ObjectOutputStream oos = new ObjectOutputStream(fos)) {
            oos.writeObject(obj);
        }
    }
    
    /**
     * 从文件反序列化
     */
    @SuppressWarnings("unchecked")
    public static <T> T deserializeFromFile(String filePath) 
            throws IOException, ClassNotFoundException {
        try (FileInputStream fis = new FileInputStream(filePath);
             ObjectInputStream ois = new ObjectInputStream(fis)) {
            return (T) ois.readObject();
        }
    }
    
    /**
     * 深度克隆(通过序列化实现)
     */
    @SuppressWarnings("unchecked")
    public static <T> T deepClone(T obj) {
        if (obj == null) {
            return null;
        }
        byte[] data = serialize(obj);
        return (T) deserialize(data);
    }
    
    /**
     * 判断对象是否可序列化
     */
    public static boolean isSerializable(Object obj) {
        return obj instanceof Serializable;
    }
    
    /**
     * 获取序列化后的大小(字节)
     */
    public static int getSerializedSize(Object obj) {
        byte[] data = serialize(obj);
        return data == null ? 0 : data.length;
    }
}

// 使用示例
class SerializationDemo {
    public static void main(String[] args) throws Exception {
        User user = new User("张三", 25, "zhangsan@example.com");
        
        // 1. 字节数组序列化
        byte[] data = SerializationUtils.serialize(user);
        System.out.println("序列化大小: " + data.length + " bytes");
        
        User recovered = SerializationUtils.deserialize(data);
        System.out.println("恢复对象: " + recovered);
        
        // 2. Base64 传输
        String base64 = SerializationUtils.serializeToBase64(user);
        System.out.println("Base64: " + base64);
        
        User fromBase64 = SerializationUtils.deserializeFromBase64(base64);
        System.out.println("从 Base64 恢复: " + fromBase64);
        
        // 3. 深度克隆
        User cloned = SerializationUtils.deepClone(user);
        System.out.println("克隆对象: " + cloned);
        System.out.println("克隆与原对象是否相同: " + (user == cloned));
        System.out.println("克隆与原对象是否相等: " + user.equals(cloned));
        
        // 4. 文件序列化
        SerializationUtils.serializeToFile(user, "user.ser");
        User fromFile = SerializationUtils.deserializeFromFile("user.ser");
        System.out.println("从文件恢复: " + fromFile);
    }
}

十、总结

10.1 核心要点

  1. 标记接口Serializable 是标记接口,没有任何方法
  2. 版本控制:始终显式声明 serialVersionUID
  3. 敏感字段:使用 transient 跳过不需要序列化的字段
  4. 自定义控制:实现 writeObject/readObjectExternalizable
  5. 继承注意:父类未实现 Serializable 时需要无参构造器
  6. 安全考虑:反序列化时需要验证数据,防止攻击

10.2 最佳实践清单

  • ✅ 所有可序列化的类都声明 serialVersionUID
  • ✅ 敏感信息(密码、密钥)标记为 transient
  • ✅ 不可序列化的字段标记为 transient
  • ✅ 在 readObject 中验证反序列化的数据
  • ✅ 单例类实现 readResolve 防止破坏
  • ✅ 使用 SerializationUtils 工具类统一处理
  • ❌ 不要序列化运行时状态(Thread、Stream、Socket)
  • ❌ 不要在序列化后修改类的非兼容性结构
  • ❌ 不要依赖默认序列化处理敏感数据

10.3 何时使用 Java 序列化

场景 推荐程度
Java 原生 RMI ✅ 必须使用
短期缓存(JVM 间) ✅ 推荐
Session 钝化 ✅ 推荐
深度克隆 ✅ 简单实现
长期存储 ⚠️ 考虑版本演进
跨语言通信 ❌ 使用 Protobuf/JSON
高性能场景 ❌ 使用 Kryo/Protobuf

Java 序列化是一个强大而精细的机制,理解其工作原理和最佳实践,能让你在面对分布式、持久化等场景时游刃有余。记住:简单场景用默认序列化,复杂场景用自定义控制,性能敏感用第三方框架。