Java 序列化与反序列化

Java 序列化与反序列化

序列化与反序列化的概念

核心定义

  • 序列化:将内存中的 Java 对象转换成字节序列(二进制数据)的过程。目的是实现对象的持久化存储(如保存到文件)或网络传输(如跨进程、跨服务器传递对象)。
  • 反序列化:将序列化后的字节序列还原为原始 Java 对象的过程。从文件、网络流中读取字节数据,恢复为可操作的对象实例。

核心依赖

要让一个对象支持序列化,其所属的类必须实现 java.io.Serializable 接口。该接口是标记接口(无抽象方法),仅用于告知 JVM 该类对象可被序列化。

序列化的关键要求

实现 Serializable 接口

类声明时显式实现 Serializable,否则序列化时会抛出 NotSerializableException 异常。

序列化版本号(serialVersionUID)

  • 须在序列化类中声明静态常量 serialVersionUID(格式:private static final long serialVersionUID = 版本号L;)。
  • 作用:用于验证序列化与反序列化时的类结构一致性。若类结构修改(如增减属性、修改方法),但 serialVersionUID 不变,反序列化仍可兼容旧版本字节序列;若未声明,JVM 会自动计算,类结构微小变化会导致版本号改变,反序列化失败。
  • 生成方式:可手动指定(如 1L),或使用 IDE 插件(如 GenerateSerialVersionUID)自动生成唯一值。
    image

不参与序列化的属性

  • 静态属性:被 static 修饰的属性属于类级别的数据,不随对象实例变化,默认不参与序列化。
  • ** transient 修饰的非静态属性**:若非静态属性不想被序列化,可添加 transient 关键字修饰,序列化时会忽略该属性,反序列化后该属性会恢复为默认值(如 null0)。

完整示例:对象的序列化与反序列化

定义可序列化的实体类(Student)

package com.io.entity;

import java.io.Serializable;

/**
 * 可序列化的学生类
 * 必须实现 Serializable 接口,并声明 serialVersionUID
 * 否则会抛出 InvalidClassException
 * transient :不参与序列化
 * @author Jing61
 */
public class Student implements Serializable {
    // 序列化版本号,确保序列化与反序列化的兼容性
    private static final long serialVersionUID = 1L;

    private String name;
    private int age;
    private String sex;
    private String address;
    private String phone;
    // 静态属性:不参与序列化
    public static String school = "No.1 Middle School";
    // transient 修饰的属性:不参与序列化
    private transient String idCard;

    public Student(String name, int age, String sex, String address, String phone, String idCard) {
        this.name = name;
        this.age = age;
        this.sex = sex;
        this.address = address;
        this.phone = phone;
        this.idCard = idCard;
    }

    // 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;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }

    public String getIdCard() {
        return idCard;
    }

    public void setIdCard(String idCard) {
        this.idCard = idCard;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", sex='" + sex + '\'' +
                ", address='" + address + '\'' +
                ", phone='" + phone + '\'' +
                ", school='" + school + '\'' + // 静态属性(非序列化字段)
                ", idCard='" + idCard + '\'' + // transient 属性(非序列化字段)
                '}';
    }
}

序列化与反序列化工具类(ObjectStream)

使用 ObjectOutputStream 实现序列化(写入字节序列),ObjectInputStream 实现反序列化(读取字节序列)。推荐使用 try-with-resources 自动关闭流资源。

package com.io.io;

import com.io.entity.Student;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

/**
 * 对象序列化与反序列化工具类
 */
public class ObjectStream {

    /**
     * 序列化:将 Student 对象写入文件(student.dat)
     */
    public static void write() {
        // 创建待序列化的对象(包含 transient 属性和静态属性)
        Student student = new Student("peppa", 18, "Female", "beijing", "123456", "110101200501011234");

        // try-with-resources 自动关闭 ObjectOutputStream 和 FileOutputStream
        try (var os = new ObjectOutputStream(new FileOutputStream("student.dat"))) {
            os.writeObject(student); // 写入对象(序列化核心操作)
            System.out.println("序列化成功:对象已写入 student.dat");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 反序列化:从文件(student.dat)读取字节序列,还原为 Student 对象
     */
    public static void read() {
        // try-with-resources 自动关闭 ObjectInputStream 和 FileInputStream
        try (var in = new ObjectInputStream(new FileInputStream("student.dat"))) {
            Student student = (Student) in.readObject(); // 读取对象(反序列化核心操作)
            System.out.println("反序列化成功,还原的对象:");
            System.out.println(student.toString());
            // 验证:transient 属性 idCard 为 null(未序列化),静态属性 school 为当前类的默认值(非序列化字段)
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        write(); // 执行序列化(先运行一次写入文件)
        // read(); // 执行反序列化(读取文件中的对象)
    }
}

运行结果与说明

序列化执行结果

  • 运行 write() 方法后,项目根目录会生成 student.dat 文件(二进制文件,无法直接打开查看)。
序列化成功:对象已写入 student.dat

image

反序列化执行结果

  • 注释 write(),运行 read() 方法,控制台输出:
反序列化成功,还原的对象:
Student{name='peppa', age=18, sex='Female', address='beijing', phone='123456', school='No.1 Middle School', idCard='null'}

image

  • 关键结论:
    • 常规属性(name、age、sex 等)正常序列化和反序列化;
    • 静态属性 school 显示当前类的默认值(非序列化字段,未从文件读取);
    • transient 属性 idCardnull(未参与序列化,反序列化后恢复默认值)。

注意事项

  1. 序列化类的所有成员必须可序列化:若类中包含其他引用类型属性,该引用类型也必须实现 Serializable 接口,否则序列化会失败。
  2. 避免序列化敏感数据:如密码、身份证号等敏感信息,可使用 transient 修饰,或在序列化前加密、脱敏。
  3. 文件兼容性:序列化后的 .dat 文件与 serialVersionUID 强绑定,类结构修改后需保持版本号一致,否则无法反序列化旧文件。
  4. 流关闭ObjectOutputStreamObjectInputStream 是资源对象,必须关闭(推荐 try-with-resources),否则会导致资源泄露。
posted @ 2025-11-11 11:27  Jing61  阅读(31)  评论(0)    收藏  举报