Java中的序列化机制

Java中的序列化机制详解

序列化是Java中一种重要的对象持久化和传输机制,它允许将对象转换为字节流,以便存储到文件、数据库或通过网络传输。下面我将全面介绍Java序列化的概念、实现方式、应用场景及注意事项。

一、序列化的基本概念

序列化 是指将Java对象转换为字节序列的过程,而反序列化则是将字节序列恢复为Java对象的过程。这种机制使得对象可以在不同平台、不同JVM之间传输和存储。

序列化的核心目的是实现:

  • 对象持久化:将对象状态保存到磁盘文件中
  • 网络传输:通过网络传输对象数据
  • 跨平台共享:在不同系统间交换对象数据
  • 深拷贝:通过序列化/反序列化实现对象的深度复制

Java的序列化机制是通过运行时判断类的序列化ID(serialVersionUID)来判定版本的一致性。在反序列化时,Java虚拟机会通过二进制流中的serialVersionUID与本地的对应实体类进行比较,如果相同就认为是一致的。

二、序列化的实现方式

1. JDK原生序列化

Java提供了两种主要的原生序列化方式:

(1) Serializable接口

Serializable接口是一个标记接口,没有方法或字段。一旦实现了此接口,就标志该类的对象就是可序列化的。

这是最简单的序列化方式,只需让类实现java.io.Serializable标记接口即可:

java 复制代码
import java.io.Serializable;
​
public class Person implements Serializable {
    private static final long serialVersionUID = 1L;
    private String name;
    private int age;
    // 构造方法、getter/setter省略
}

序列化和反序列化的基本代码示例:

java 复制代码
// 序列化
//ObjectOutputStream 表示对象输出流,它的writeObject(Object obj)方法可以对指定obj对象参数进行序列化,再把得到的字节序列写到一个目标输出流中。
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.ser"))) {
    oos.writeObject(person);
}
​
// 反序列化
//ObjectInputStream 表示对象输入流, 它的readObject()方法,从输入流中读取到字节序列,反序列化成为一个对象,最后将其返回。
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("person.ser"))) {
    Person deserializedPerson = (Person) ois.readObject();
}
//.ser 这是 Java 开发中常用的序列化文件扩展名,表示文件中存储的是序列化后的对象。
(2) Externalizable接口

Externalizable继承了Serializable接口,还定义了两个抽象方法:writeExternal()和readExternal(),如果开发人员使用Externalizable来实现序列化和反序列化,需要重写writeExternal()和readExternal()方法。

ExternalizableSerializable的子接口,允许开发者完全控制序列化过程:

java 复制代码
// 实现Externalizable接口以自定义序列化过程,需手动控制读写逻辑
public class Employee implements Externalizable {
    private String name;
    private int salary;
    
    // 必须提供无参构造方法,因为Externalizable反序列化时会通过反射创建实例
    public Employee() {}
    
    @Override
    // 自定义序列化写入逻辑:按顺序写入对象的字段
    public void writeExternal(ObjectOutput out) throws IOException {
        // 写入字符串对象(支持非基本类型)
        out.writeObject(name);
        // 写入整型基本类型
        out.writeInt(salary);
    }
    
    @Override
    // 自定义反序列化读取逻辑:按写入顺序读取并恢复对象状态
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        // 读取顺序必须与写入完全一致!
        // 读取String对象并强制类型转换
        this.name = (String) in.readObject();
        // 读取int基本类型
        this.salary = in.readInt();
    }
    
    // 其他方法(例如getter/setter、业务方法等)在此省略...
}

三、序列化的关键特性

1. serialVersionUID

serialVersionUID是序列化版本号,用于标识类的版本:

arduino 复制代码
private static final long serialVersionUID = 1L;

serialVersionUID有什么用

  • 确保序列化和反序列化的类版本一致
  • 避免因类结构变化导致的InvalidClassException
  • 建议显式声明而非使用JVM默认生成的UID
  • JAVA序列化的机制是通过判断类的serialVersionUID来验证版本是否一致的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID和本地相应实体类的serialVersionUID进行比较,如果相同,反序列化成功,如果不相同,就抛出InvalidClassException异常。

2. transient关键字

transient用于标记不需要序列化的字段:

arduino 复制代码
private transient String password;  // 该字段不会被序列化

反序列化后,transient字段会被设为默认值(对象为null,基本类型为0等)。

3. static静态字段

静态变量属于类而非对象,因此不会被序列化。反序列化后,静态字段的值将是当前JVM中该类的静态字段值。

4.其他

  • 如果某个序列化类的成员变量是对象类型,则该对象类型的类必须实现序列化;
  • 子类实现了Serializable,父类没有实现Serializable接口的话,父类不会被序列化

四、序列化的应用场景

Java序列化在多种场景下发挥重要作用:

  1. 远程方法调用(RMI) :实现分布式对象通信
  2. 对象持久化:将对象状态保存到文件或数据库
  3. 网络传输:如HTTP接口传输复杂对象
  4. 缓存机制:如Redis等缓存系统中的对象存储
  5. 深拷贝:通过序列化/反序列化实现对象深度复制
  6. 分布式计算:不同节点间的数据交换
  7. 会话管理:Web应用中的会话对象存储
  8. 消息队列:JMS等消息系统中的消息对象传输

五、代码示例:完整序列化流程

typescript 复制代码
import java.io.*;
​
public class SerializationDemo {
    public static void main(String[] args) {
        // 创建对象
        User user = new User("张三", 25, "zhangsan", "password123");
        
        // 序列化
        try (ObjectOutputStream oos = new ObjectOutputStream(
                new FileOutputStream("user.ser"))) {
            oos.writeObject(user);
            System.out.println("对象已序列化");
        } catch (IOException e) {
            e.printStackTrace();
        }
        
        // 反序列化
        try (ObjectInputStream ois = new ObjectInputStream(
                new FileInputStream("user.ser"))) {
            User deserializedUser = (User) ois.readObject();
            System.out.println("反序列化结果: " + deserializedUser);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}
​
//声明一个实体类,实现Serializable接口
 class User implements Serializable {
    private static final long serialVersionUID = 1L; // 序列化版本UID
​
    private String name;
    private int age;
    private String username;
    private transient String password; // 不被序列化的字段
​
    public User(String name, int age, String username, String password) {
        this.name = name;
        this.age = age;
        this.username = username;
        this.password = password;
    }
​
    // 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 getUsername() {
        return username;
    }
​
    public void setUsername(String username) {
        this.username = username;
    }
​
    public String getPassword() {
        return password;
    }
​
    public void setPassword(String password) {
        this.password = password;
    }
}
相关推荐
罗技12329 分钟前
ES类的索引轮换
java·linux·elasticsearch
liaokailin1 小时前
Spring AI 实战:第十一章、Spring AI Agent之知行合一
java·人工智能·spring
JANYI20182 小时前
C文件在C++平台编译时的注意事项
java·c语言·c++
benpaodeDD3 小时前
双列集合——map集合和三种遍历方式
java
Q_Boom4 小时前
前端跨域问题怎么在后端解决
java·前端·后端·spring
搬砖工程师Cola4 小时前
<Revit二次开发> 通过一组模型线构成墙面,并生成墙。Create(Document, IList.Curve., Boolean)
java·前端·javascript
等什么君!4 小时前
学习spring boot-拦截器Interceptor,过滤器Filter
java·spring boot·学习
caihuayuan44 小时前
Linux环境部署iview-admin项目
java·大数据·sql·spring·课程设计
浪前4 小时前
【项目篇之统一内存操作】仿照RabbitMQ模拟实现消息队列
java·分布式·rabbitmq·ruby
奋进的小暄4 小时前
数据结构(4) 堆
java·数据结构·c++·python·算法