第61章

Java序列化

对象的序列化和反序列化详解

序列化概述

Java序列化是将对象的状态转换为字节流的过程,以便可以将对象保存到文件、数据库或通过网络传输。反序列化是相反的过程,将字节流转换回对象。序列化机制是Java平台的重要特性,广泛应用于分布式系统、缓存、持久化存储等场景。

持久化存储

将对象保存到文件或数据库中,实现数据的长期保存。

网络传输

在分布式系统中传输对象数据,实现远程方法调用。

深度复制

创建对象的完整副本,包括所有嵌套对象。

缓存机制

将对象存储在缓存中,提高系统性能。

Serializable接口

要使一个类的对象可以被序列化,该类必须实现Serializable接口。这是一个标记接口,没有任何方法,仅用于标识该类支持序列化。

import java.io.Serializable;

public class Student implements Serializable {
    private static final long serialVersionUID = 1L;
    
    private String name;
    private int age;
    private String studentId;
    
    public Student(String name, int age, String studentId) {
        this.name = name;
        this.age = age;
        this.studentId = studentId;
    }
    
    // getter和setter方法
    public String getName() { return name; }
    public int getAge() { return age; }
    public String getStudentId() { return studentId; }
}

重要提示

建议显式声明serialVersionUID,这是一个版本控制字段,用于确保序列化和反序列化的兼容性。如果不声明,JVM会自动生成,但类结构的任何变化都可能导致版本不匹配。

基础序列化操作

序列化过程

使用ObjectOutputStream类将对象写入字节流:

public static void serializeStudent(Student student, String filename) throws IOException {
    try (FileOutputStream fileOut = new FileOutputStream(filename);
         ObjectOutputStream out = new ObjectOutputStream(fileOut)) {
        
        out.writeObject(student);
        System.out.println("学生对象已序列化到文件: " + filename);
    }
}

反序列化过程

使用ObjectInputStream类从字节流中读取对象:

public static Student deserializeStudent(String filename) 
        throws IOException, ClassNotFoundException {
    
    try (FileInputStream fileIn = new FileInputStream(filename);
         ObjectInputStream in = new ObjectInputStream(fileIn)) {
        
        Student student = (Student) in.readObject();
        System.out.println("学生对象已从文件反序列化: " + filename);
        return student;
    }
}
💻 查看完整代码 - 在线IDE体验

transient关键字

transient关键字用于标记不需要序列化的字段。被transient修饰的字段在序列化时会被忽略,反序列化时这些字段会被设置为默认值。

public class User implements Serializable {
    private static final long serialVersionUID = 1L;
    
    private String username;
    private String email;
    
    // 密码字段不序列化,保护敏感信息
    private transient String password;
    
    // 临时计算字段,不需要序列化
    private transient int loginCount;
    
    // 静态字段不会被序列化
    private static String systemVersion = "1.0";
}

安全考虑

密码、密钥等敏感信息应使用transient修饰,避免被序列化。

临时数据

缓存、计算结果等临时数据不需要序列化,可以在反序列化后重新计算。

不可序列化对象

包含不可序列化对象的字段应使用transient修饰。

内存优化

大型对象或集合可以使用transient减少序列化数据大小。

序列化版本控制

serialVersionUID是序列化版本控制的核心机制,用于确保序列化和反序列化过程中的类版本兼容性。

版本兼容性规则

兼容的变化

  • 添加新字段
  • 删除字段
  • 改变字段的访问修饰符
  • 将字段从static改为非static
  • 将字段从transient改为非transient

不兼容的变化

  • 改变字段类型
  • 改变类的继承结构
  • 删除Serializable接口
  • 将字段从非static改为static
  • 将字段从非transient改为transient
public class Product implements Serializable {
    // 显式声明版本号,确保兼容性
    private static final long serialVersionUID = 1L;
    
    private String name;
    private double price;
    private String category;
    
    // 新增字段(版本升级)
    private String description = "暂无描述";
    
    // 自定义反序列化方法,处理版本兼容性
    private void readObject(ObjectInputStream in) 
            throws IOException, ClassNotFoundException {
        in.defaultReadObject();
        
        // 为新字段设置默认值
        if (description == null) {
            description = "暂无描述";
        }
    }
}

序列化最佳实践

推荐做法

版本控制

// 显式声明serialVersionUID
private static final long serialVersionUID = 1L;
  • 始终显式声明serialVersionUID
  • 使用有意义的版本号
  • 文档化版本变更

安全考虑

// 敏感字段不序列化
private transient String password;
private transient String secretKey;
  • 使用transient保护敏感信息
  • 验证反序列化后的对象状态
  • 考虑使用白名单机制

性能优化

// 使用缓冲流提高性能
ObjectOutputStream out = new ObjectOutputStream(
    new BufferedOutputStream(new FileOutputStream("file.ser")));

// 自定义序列化方法
private void writeObject(ObjectOutputStream out) throws IOException {
    out.defaultWriteObject();
    // 自定义序列化逻辑
}

private void readObject(ObjectInputStream in) 
        throws IOException, ClassNotFoundException {
    in.defaultReadObject();
    // 自定义反序列化逻辑,初始化transient字段
}

常见问题和解决方案

NotSerializableException

原因:类未实现Serializable接口或包含不可序列化的字段

解决:实现Serializable接口,或使用transient标记不可序列化字段

InvalidClassException

原因:serialVersionUID不匹配,类结构发生变化

解决:显式声明serialVersionUID,确保版本兼容性

性能问题

原因:序列化大对象或频繁序列化操作

解决:使用BufferedStream,避免序列化不必要的字段

安全问题

原因:反序列化不可信数据可能导致安全漏洞

解决:验证反序列化对象,使用白名单机制

序列化替代方案

虽然Java原生序列化功能强大,但在某些场景下,其他序列化方案可能更适合:

JSON序列化

使用Jackson、Gson等库,跨语言兼容性好,可读性强。

XML序列化

使用JAXB等技术,标准化程度高,支持复杂数据结构。

Protocol Buffers

Google开发的序列化框架,性能优异,数据紧凑。

Apache Avro

支持模式演化的数据序列化系统,适合大数据场景。