【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 关键字。

核心规则

  1. transient 仅能修饰成员变量,不能修饰类、方法;
  2. transient 修饰的字段,不会参与序列化;
  3. 反序列化后,该字段会被赋予数据类型默认值(引用类型 null、数值类型 0、布尔类型 false);
  4. 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,可以让开发者手动定义序列化、反序列化规则,粒度控制更强。

核心特点

  1. 必须重写 writeExternal()readExternal() 两个方法,手动指定需要序列化的字段;
  2. 强制要求类提供无参构造方法,反序列化时会先调用无参构造创建对象;
  3. transient 关键字对它失效,序列化规则完全由开发者手写;
  4. 相比自动序列化,性能更高,但编码成本更大。

代码实战

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 对比总结

特性SerializableExternalizable
方法实现空接口,无需重写方法必须重写 writeExternal /readExternal
无参构造不要求强制要求
字段控制自动序列化,transient 生效手动控制字段,transient 失效
性能较低(JVM 自动反射处理)较高(手动读写)
适用场景简单对象、快速开发敏感数据加密、高性能场景

五、serialVersionUID:序列化版本号

这是序列化中极易踩坑的知识点。serialVersionUID 是序列化版本标识符,作用是保证类版本升级后,旧的字节流依旧可以正常反序列化

工作原理

反序列化时,JVM 会对比字节流中的版本号和当前类的版本号:

  • 版本号一致:正常反序列化;
  • 版本号不一致:抛出 InvalidClassException 版本不匹配异常。

版本号三种写法

  1. 手动固定版本号(推荐)
private static final long serialVersionUID = 1L;

类结构小幅修改(新增普通字段)时,版本号不变,兼容旧数据。

  1. IDE 自动生成随机版本号
private static final long serialVersionUID = -2095916884810199532L;

类一旦修改,建议同步更新版本号,适用于版本严格隔离的场景。

  1. 使用注解抑制警告
@SuppressWarnings("serial")

踩坑演示

如果不手动定义 serialVersionUID,JVM 会根据类结构自动生成版本号。一旦修改类(新增 / 删除字段、修改方法),自动版本号就会变更,旧文件将无法反序列化。

最佳实践:所有实现 Serializable 的类,都手动声明 serialVersionUID = 1L,最大程度兼容版本迭代。

六、整体总结

  1. 序列化核心:实现 Serializable 空接口,依靠 ObjectOutputStream / ObjectInputStream 完成读写;
  2. transient 用于临时屏蔽对象字段,静态变量天然不参与序列化;
  3. Externalizable 适合精细化控制序列化逻辑,必须实现两个抽象方法 + 无参构造;
  4. serialVersionUID 是版本兼容的关键,开发中务必手动定义,避免线上版本报错。

Java 序列化在 IO 流、网络编程、缓存框架中使用十分广泛,理解底层规则和边界问题,才能在开发中避开各类异常。

Tags:

发表回复

Your email address will not be published. Required fields are marked *.

*
*