知识点:Java 中的序列化与反序列化

一、什么是序列化与反序列化

在 Java 编程中,序列化(Serialization)是指将一个对象转换为字节流的过程,以便能够在网络上传输、存储到文件系统或数据库等持久化存储介质中。而反序列化(Deserialization)则是将字节流重新恢复为对象的逆过程。

想象你有一个复杂的对象,比如一个包含用户信息(姓名、年龄、地址等)的 User 对象。如果要将这个对象通过网络发送给其他程序,或者保存到文件中,就需要先将其转换为字节序列。接收方或从文件读取数据的程序,再将这些字节序列还原成原来的 User 对象,这就是序列化与反序列化的过程。

二、为什么需要序列化与反序列化

  1. 网络传输:在分布式系统中,不同的节点之间经常需要交换对象信息。例如,一个服务器端的 Java 程序可能需要将一些数据对象发送给客户端,或者在多个服务器之间传递数据。由于网络传输的数据格式通常是字节流,所以需要将对象序列化后才能传输,接收方再进行反序列化恢复对象。
  2. 数据持久化:当需要将对象保存到文件或数据库中时,对象不能直接存储,必须转换为字节形式。这样下次程序运行时,可以从持久化存储中读取字节流并反序列化为对象,恢复程序的状态。比如,游戏程序可能需要保存玩家的游戏进度,将玩家的游戏数据对象(包含等级、装备等信息)序列化后保存到文件,下次玩家启动游戏时再反序列化恢复游戏状态。

三、如何实现序列化与反序列化

在 Java 中,实现序列化与反序列化非常简单,只需让类实现 java.io.Serializable 接口即可。这个接口是一个标记接口,没有任何方法,只是告诉 Java 虚拟机这个类的对象可以被序列化。

简单示例

  1. 定义可序列化的类
java 复制代码
import java.io.Serializable;

public class User implements Serializable {
    private static final long serialVersionUID = 1L;
    private String name;
    private int age;

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

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }
}

这里的 serialVersionUID 是一个版本号,用于在反序列化时验证序列化对象的版本与当前类的版本是否兼容。如果序列化对象的版本号与当前类的版本号不一致,反序列化可能会失败。虽然可以不手动定义 serialVersionUID,Java 会自动生成一个,但手动定义可以更好地控制版本兼容性。

  1. 序列化对象
java 复制代码
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class SerializeExample {
    public static void main(String[] args) {
        User user = new User("Alice", 25);
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("user.ser"))) {
            oos.writeObject(user);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

在这个例子中,我们创建了一个 User 对象,并使用 ObjectOutputStream 将其写入名为 user.ser 的文件中。ObjectOutputStream 负责将对象转换为字节流并写入输出流。

  1. 反序列化对象
java 复制代码
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;

public class DeserializeExample {
    public static void main(String[] args) {
        try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("user.ser"))) {
            User user = (User) ois.readObject();
            System.out.println("Name: " + user.getName());
            System.out.println("Age: " + user.getAge());
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

这里使用 ObjectInputStreamuser.ser 文件中读取字节流,并将其反序列化为 User 对象。readObject 方法会返回一个 Object 类型的对象,需要将其强制转换为原来的 User 类型。

四、序列化的注意事项

  1. 静态变量不参与序列化:静态变量属于类级别,不属于对象的状态,因此不会被序列化。例如:
java 复制代码
import java.io.Serializable;

public class StaticVarExample implements Serializable {
    private static int staticVar = 10;
    private int instanceVar;

    public StaticVarExample(int instanceVar) {
        this.instanceVar = instanceVar;
    }

    public static int getStaticVar() {
        return staticVar;
    }

    public int getInstanceVar() {
        return instanceVar;
    }
}

在序列化和反序列化 StaticVarExample 对象时,staticVar 的值不会被保存和恢复。反序列化后的对象中,staticVar 的值取决于当前类加载后的状态,而不是序列化时的值。

  1. 瞬态变量不参与序列化 :使用 transient 关键字修饰的变量称为瞬态变量,不会被序列化。这通常用于保护敏感信息或者一些不需要持久化的临时状态。例如:
java 复制代码
import java.io.Serializable;

public class TransientExample implements Serializable {
    private String normalVar = "This is normal";
    private transient String sensitiveVar = "Secret";

    public String getNormalVar() {
        return normalVar;
    }

    public String getSensitiveVar() {
        return sensitiveVar;
    }
}

在序列化 TransientExample 对象时,sensitiveVar 的值不会被包含在字节流中,反序列化后 sensitiveVar 的值为 null

  1. 类继承与序列化 :如果一个类实现了 Serializable 接口,它的子类也默认是可序列化的,除非子类明确不实现该接口。如果父类没有实现 Serializable 接口,子类实现了,那么在反序列化子类对象时,父类的构造函数会被调用,以初始化父类部分的状态。

  2. 版本兼容性 :如前文提到的 serialVersionUID,它在版本兼容性方面起着关键作用。如果类的结构发生了变化(例如添加或删除字段、修改字段类型等),手动更新 serialVersionUID 可以避免反序列化错误。如果没有手动定义 serialVersionUID,Java 会根据类的结构自动生成一个。但这种自动生成的方式在类结构变化时可能导致兼容性问题,因为即使类的语义变化不大,结构上的微小改变也可能导致生成不同的 serialVersionUID

序列化与反序列化是 Java 编程中非常重要的技术,在网络通信、数据持久化等领域有着广泛的应用。理解并正确使用它们,可以确保对象在不同环境和时间中的正确传输与恢复。

相关推荐
mzlogin1 分钟前
Java|小数据量场景的模糊搜索体验优化
java·后端
lozhyf24 分钟前
基于springboot的商城
java·spring boot·后端
无心水25 分钟前
【Java面试笔记:基础】6.动态代理是基于什么原理?
java·笔记·面试·动态代理·cglib·jdk动态代理
小爷毛毛_卓寿杰41 分钟前
【Dify(v1.2) 核心源码深入解析】App 模块:Entities、Features 和 Task Pipeline
人工智能·后端·python
java奋斗者1 小时前
健身房管理系统(springboot+ssm+vue+mysql)含运行文档
spring boot·后端·mysql
努力努力再努力wz1 小时前
【c++深入系列】:万字string详解(附有sso优化版本的string模拟实现源码)
java·运维·c语言·开发语言·c++
用户638982245891 小时前
查漏补缺:Seata分布式事务的使用
后端
用户638982245891 小时前
查漏补缺:Sentinel的使用简介
后端
红豆和绿豆1 小时前
mybatis-plus开发orm
java·开发语言·mybatis
zhang98800001 小时前
利用java语言,怎样开发和利用各种开源库和内部/自定义框架,实现“提取-转换-加载”(ETL)流程的自动化
java·开源·etl