首页...Java语言程序设计——序列化与反序列化详解
Java语言程序设计第七章 输入输出流/第三节 序列化与反序列化

Java语言程序设计——序列化与反序列化详解

2026-03-24

第七章 输入输出流

第三节 序列化与反序列化

概述

序列化与反序列化是Java中进行对象持久化和数据传输的重要机制。通过序列化,可以将内存中的对象转换为字节流,方便存储或网络传输;而反序列化则是将字节流还原为Java对象。本节将全面介绍序列化与反序列化的概念、原理、实现方式以及相关注意事项,帮助考生深刻理解并掌握这一核心知识点,为全国计算机等级考试二级Java语言程序设计部分做好准备。

学习目标

  • 理解序列化和反序列化的基本概念及应用场景
  • 掌握Java中序列化与反序列化的实现方式和核心类
  • 熟悉序列化机制的原理及注意事项
  • 能够编写代码实现对象的序列化与反序列化
  • 了解常见错误及其解决方案
  • 掌握序列化在实际项目中的典型应用

核心概念

1. 序列化(Serialization)

序列化是指将Java对象转换成字节序列的过程。序列化后的对象可以被写入文件、传输到网络、存储到数据库等。该过程确保对象的状态能在不同环境中保持一致。

2. 反序列化(Deserialization)

反序列化是序列化的逆过程,将字节流转换为Java对象,使程序可以恢复之前保存的对象状态。

3. Serializable接口

这是Java中实现序列化的标记接口。只有实现了Serializable接口的类的对象才能被序列化。

4. transient关键字

用于修饰不需要序列化的成员变量,序列化时该变量不会被保存。

5. serialVersionUID

序列化版本号,用于验证序列化对象和反序列化类版本的一致性,防止版本不兼容导致错误。


原理分析

序列化机制基于Java的IO流体系,主要通过ObjectOutputStream和ObjectInputStream实现。

  • 序列化流程

    1. 对象必须实现Serializable接口。
    2. 创建ObjectOutputStream,调用writeObject()方法将对象写入输出流。
    3. JVM根据对象属性和类型信息,将对象转换成字节序列。
  • 反序列化流程

    1. 创建ObjectInputStream,调用readObject()方法读取字节流。
    2. JVM根据字节流中的信息重构对象实例。
    3. 如果serialVersionUID匹配,反序列化成功,否则抛出异常。

序列化的底层原理包含将对象的类信息、属性值以及引用关系编码到字节流中,保证对象的完整性和关联性。


详细内容

1. 实现序列化的步骤

实现序列化主要涉及以下几个方面:

  • 实现Serializable接口

    • 这是一个标记接口,不包含任何方法,仅用来标识该类支持序列化。
  • 定义serialVersionUID

    • 建议显式声明serialVersionUID,避免默认计算带来的兼容性问题。
    • 格式示例:
private static final long serialVersionUID = 1L;
  • 使用ObjectOutputStream写对象

    • 创建文件输出流(FileOutputStream),再包装ObjectOutputStream。
    • 调用writeObject()方法写入对象。
  • 使用ObjectInputStream读对象

    • 创建文件输入流(FileInputStream),再包装ObjectInputStream。
    • 调用readObject()方法读取对象。

2. transient关键字的作用

  • 用于修饰不想序列化的成员变量。
  • 在序列化时,transient变量不会被保存,反序列化后该变量为默认值。
  • 适用于保存敏感信息或非持久化数据。

3. 序列化的版本控制

  • serialVersionUID用于版本控制。
  • 如果序列化的类和反序列化的类serialVersionUID不匹配,程序会抛出InvalidClassException。
  • 建议在类定义时显式声明serialVersionUID,防止自动生成的版本号因代码变动而改变。

4. 自定义序列化

  • 可以通过实现private void writeObject(ObjectOutputStream oos)和private void readObject(ObjectInputStream ois)来定制序列化和反序列化过程。
  • 适合需要对某些字段进行特殊处理(如加密、压缩等)时使用。

5. 对象的深克隆实现

  • 利用序列化和反序列化可以实现对象的深克隆。
  • 通过将对象序列化为字节流,再反序列化为新对象,达到深复制的效果。

典型实例分析

实例一:简单序列化与反序列化

背景:实现一个学生Student类的序列化和反序列化。

import java.io.*;

class Student implements Serializable {
    private static final long serialVersionUID = 1L;
    String name;
    int age;
    transient String password; // 不序列化密码

    public Student(String name, int age, String password) {
        this.name = name;
        this.age = age;
        this.password = password;
    }

    @Override
    public String toString() {
        return "Student{name='" + name + "', age=" + age + ", password='" + password + "'}";
    }
}

public class SerializeDemo {
    public static void main(String[] args) {
        Student s = new Student("张三", 20, "123456");

        // 序列化
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("student.dat"))) {
            oos.writeObject(s);
            System.out.println("序列化成功");
        } catch (IOException e) {
            e.printStackTrace();
        }

        // 反序列化
        try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("student.dat"))) {
            Student s2 = (Student) ois.readObject();
            System.out.println("反序列化结果:" + s2);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

分析

  • password字段被transient修饰,序列化后不保存,反序列化时为null。
  • 通过文件保存和读取实现对象的持久化。

结论:掌握transient关键字和基本的序列化反序列化流程。


实例二:序列化版本控制

背景:演示serialVersionUID的作用。

步骤:

  • 定义类Person,声明serialVersionUID = 1L,序列化后保存到文件。
  • 修改Person类,添加新字段,保持serialVersionUID不变。
  • 反序列化旧文件,观察是否成功。

代码示例(简要)

class Person implements Serializable {
    private static final long serialVersionUID = 1L;
    String name;
    int age;
    // 新增字段
    String address;
}

分析

  • serialVersionUID一致,反序列化成功,新增字段address默认null。
  • 如果serialVersionUID不一致,则抛异常。

结论:显式声明serialVersionUID,保证版本兼容性。


实例三:自定义序列化

背景:对用户密码进行加密存储。

class User implements Serializable {
    private static final long serialVersionUID = 1L;
    String username;
    transient String password;

    public User(String username, String password) {
        this.username = username;
        this.password = password;
    }

    private void writeObject(ObjectOutputStream oos) throws IOException {
        oos.defaultWriteObject();
        String encrypted = encrypt(password);
        oos.writeObject(encrypted);
    }

    private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
        ois.defaultReadObject();
        String encrypted = (String) ois.readObject();
        password = decrypt(encrypted);
    }

    private String encrypt(String pwd) {
        // 简单示例,实际应更复杂
        return new StringBuilder(pwd).reverse().toString();
    }

    private String decrypt(String encryptedPwd) {
        return new StringBuilder(encryptedPwd).reverse().toString();
    }
}

分析

  • password定义为transient,避免默认序列化。
  • 重写writeObject和readObject实现加密和解密。

结论:自定义序列化满足特殊需求,增强安全性。


常见误区与注意事项

  1. 未实现Serializable接口就进行序列化

    • 错误:程序运行时抛出NotSerializableException。
    • 正确:必须实现Serializable接口。
  2. 忽略serialVersionUID的声明

    • 错误:类结构变更时,默认serialVersionUID变化,导致反序列化失败。
    • 正确:手动声明serialVersionUID,保证版本兼容。
  3. 错误使用transient关键字

    • 错误:敏感信息未用transient修饰,导致泄露。
    • 正确:敏感字段用transient修饰或自定义序列化处理。
  4. 忽略对象中引用类型的序列化

    • 错误:对象中包含未序列化的引用对象,导致序列化失败。
    • 正确:引用对象类也必须实现Serializable。
  5. 反序列化时强制类型转换错误

    • 错误:反序列化对象后未正确强转,导致ClassCastException。
    • 正确:确保类型匹配。

应用场景

  • 持久化存储:将对象状态保存到文件或数据库,便于程序重启后恢复数据。
  • 网络传输:通过序列化将对象转换为字节流,发送至远程主机,实现远程方法调用(RMI)等。
  • 缓存机制:将复杂对象序列化存入缓存,提升访问效率。
  • 深克隆:利用序列化和反序列化实现对象的深度复制。
  • 配置管理:将应用配置对象序列化保存,方便加载和修改。

知识拓展

  • Externalizable接口

    • 继承自Serializable,提供writeExternal和readExternal方法,自定义序列化流程。
    • 相比Serializable,更灵活控制序列化细节。
  • Java序列化的安全问题

    • 不安全的反序列化可能导致远程代码执行,应谨慎使用。
    • 可以采用白名单校验、使用安全框架等方法防范风险。
  • JSON与XML序列化

    • 除Java原生序列化外,常用JSON、XML格式进行数据交换。
    • 更加跨语言、可读,适合Web应用。
  • 序列化性能优化

    • 使用transient减少序列化数据量。
    • 使用第三方高性能序列化库,如Kryo、Protobuf。

总结回顾

  • 序列化是将对象转换为字节流的过程,反序列化是字节流还原为对象。
  • 序列化必须实现Serializable接口,且推荐显式声明serialVersionUID。
  • 通过transient关键字控制字段是否参与序列化。
  • 可通过重写writeObject和readObject实现自定义序列化。
  • 常见错误包括未实现Serializable、忽略serialVersionUID、未处理引用对象等。
  • 序列化广泛应用于持久化、网络传输、缓存等场景。
  • 理解并掌握序列化机制,有助于写出安全、高效且兼容性良好的Java程序。

掌握本节内容是全国计算机等级考试二级Java语言程序设计的重要知识点,建议考生反复练习代码,实现理论与实践结合,提升综合能力。


参考资料

  • 《Java核心技术 卷I》—— Cay S. Horstmann
  • 《Java编程思想》—— Bruce Eckel
  • Oracle官方Java文档 - Serialization

重点知识点

1

序列化是将对象转换为字节流,反序列化是字节流还原为对象

2

实现序列化必须实现Serializable接口

3

serialVersionUID用于版本控制,确保反序列化兼容性

4

transient关键字用于标记不序列化的字段

5

ObjectOutputStream和ObjectInputStream是序列化和反序列化的核心类

6

自定义序列化通过重写writeObject和readObject实现

7

序列化对象中引用的类也必须实现Serializable接口

8

反序列化时需注意类型匹配,避免ClassCastException

9

序列化广泛应用于持久化存储、网络传输、缓存和深克隆

10

不恰当的序列化可能带来安全风险,需注意防范