private static final long serialVersionUID = -671958543348052007L; // 防止因为修改类属性导致反序列化失败,最好设置一个serialVersionUID。

什么是 Serializable接口?

Serializable 接口是 Java 编程语言中的一个接口,用于标识类的对象可以被序列化(Serialization)。

序列化和反序列化

  • 序列化:是将对象转换为字节流或文本格式,以便在网络上传输、存储到文件中或在不同的 Java 虚拟机之间进行通信。
  • 反序列化:是将序列化后的数据重新转换为对象的过程,以便在程序中使用。

白话就是:Serializable序列化的标记

为什么需要Serializable接口?(可不用记忆)

Serializable 接口提供了一种标准化的机制,使得Java对象可以在不同的环境中进行序列化和反序列化操作,实现对象的持久化、数据传输和远程方法调用等功能。

  1. 对象持久化:通过将对象序列化为字节流,可以将对象保存到文件系统、数据库或其他持久化存储介质中。这样,在程序重新启动后,可以从存储介质中读取序列化的对象,并将其反序列化为内存中的对象,恢复对象的状态和数据。
  2. 对象传输:在分布式系统或网络通信中,需要将对象从一个节点传输到另一个节点。通过序列化对象,可以将对象转换为字节流,在网络中传输,并在接收端进行反序列化,重新创建对象。这样,可以方便地在不同的计算机或不同的 Java 虚拟机之间传递对象。
  3. 远程方法调用(RPC):在远程方法调用中,客户端和服务器之间需要传递参数和返回值。通过序列化和反序列化,可以将参数和返回值转换为字节流,在网络中传输。这样,可以实现跨网络的方法调用,使得客户端可以调用位于远程服务器上的方法,并获取返回结果。
  4. 对象复制:有时候需要创建一个对象的副本,可以通过序列化和反序列化来实现。通过将对象序列化为字节流,然后反序列化为另一个对象,可以在内存中创建一个与原始对象相同的副本。

上代码实验

├── Main.java
└── entity
    └── User.java

定义一个User类

import lombok.AllArgsConstructor;
import lombok.Data;

/**
 * @author : zanglikun
 * @date : 2024/6/12 下午4:13
 * @desc : Copyright © zanglikun.com
 */
@Data // 自己引入Lombok哦,不引入自己手写构造Get/Set方法
@AllArgsConstructor
public class User {
    private String userName;
}

创建一个Main类,并实现:将User新对象序列化一下到本地,同时反序列化一下。

import entity.User;
import lombok.SneakyThrows;

import java.io.*;

/**
 * @author : zanglikun
 * @date : 2024/6/12 下午4:13
 * @desc : Copyright © zanglikun.com
 */
public class Main {

    private static String filePath = "序列化学习.txt";
    
    @SneakyThrows
    public static void main(String[] args) {
        User user = new User("张三");
        serialize(user); // 序列化到本地文件
        deserialization(); // 将本地文件发序列化
    }


    private static void serialize(User user) throws IOException {
        // 将 User 对象序列化到文件中
        try (FileOutputStream fos = new FileOutputStream(filePath)) {
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(user);
            oos.close();
        }
    }

    private static void deserialization() throws IOException, ClassNotFoundException {
        // 从文件中反序列化 User 对象
        try (FileInputStream fis = new FileInputStream(filePath)) {
            ObjectInputStream ois = new ObjectInputStream(fis);
            User deserializedUser = (User) ois.readObject();
            System.out.println("反序列化后的用户名:" + deserializedUser.getUserName());
            ois.close();
        }
    }
}

会报错

Exception in thread "main" java.io.NotSerializableException: entity.User
	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
	at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
	at Main.serialize(Main.java:25)
	at Main.main(Main.java:16)

修改User 添加:implements Serializable

import lombok.AllArgsConstructor;
import lombok.Data;

import java.io.Serializable;

/**
 * @author : zanglikun
 * @date : 2024/6/12 下午4:13
 * @desc : Copyright © zanglikun.com
 */
@Data // 自己引入Lombok哦,不引入自己手写构造Get/Set方法
@AllArgsConstructor
public class User implements Serializable {
    private String userName;
}

成功

反序列化后的用户名:张三

进程已结束,退出代码为 0

本次给User添加一个age参数

public class User implements Serializable {
    private String userName;
    private String age;
}

注释序列化代码,只执行反序列化代码

    @SneakyThrows
    public static void main(String[] args) {
        // User user = new User("张三");
        // serialize(user); // 序列化到本地文件
        deserialization(); // 将本地文件发序列化
    }
Exception in thread "main" java.io.InvalidClassException: entity.User; local class incompatible: stream classdesc serialVersionUID = 1874829408147107284, local class serialVersionUID = -4123255951891179523
	at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:699)
	at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:2005)
	at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1852)
	at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2186)
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1669)
	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:503)
	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:461)
	at Main.deserialization(Main.java:36)
	at Main.main(Main.java:19)

因为:序列化的时候 serialVersionUID 是 JVM 自动根据 class的信息生成的,你现在User类的信息变量,就会导致 serialVersionUID 发生变化!当然反序列化检查 serialVersionUID 是否一致的时候,肯定不认了,就抛出异常:InvalidClassException 因为:序列化的时候 serialVersionUID 是JVM自动根据class的信息生成的,你现在User类的信息变量,就会导致 serialVersionUID 发生变化!当然反序列化检查 serialVersionUID 是否一致的时候,肯定不认了,就抛出异常:InvalidClassException

所以我们一开始序列化前,就需要显式指定 serialVersionUID ,这样在我们修改类信息的时候,JVM反序列化读取 serialVersionUID 一模一样 的就不会有问题!

那我们知道后,序列化、反序列化的时候需要操作什么?

在需要反序列化的实体类上,加上 implements Serializable

同时 在被反序列化的属性上加上

private static final long serialVersionUID = -671958543348052007L;

抛出疑问?

Java前后端交互为啥没加serialVersionUID,也没走序列化?

在Java中,如果需要将对象序列化为字节流或文本格式以便传输或存储,通常会实现 Serializable 接口,并且可以定义 serialVersionUID 来确保序列化和反序列化的兼容性。

在Spring项目中,当后端将Java对象转换为JSON字符串返回给前端时,通常不需要显式实现 Serializable 接口。这是因为在这种情况下,Java对象并不是被序列化为字节流,而是被转换为JSON字符串,这个过程是由JSON库(比如Jackson)来处理的,而不是Java的序列化机制。因此,虽然有数据转换的过程,但并不涉及Java对象的标准序列化和反序列化过程。

Jackson等序列化库实现的不是标准的序列化

在使用Jackson(或其他类似的库,比如Gson)将Java对象转换为JSON字符串时,实际上会涉及到一种形式的序列化过程,但这与Java标准序列化机制是有区别的。

在这种情况下,Jackson库会通过反射等机制检查Java对象的结构,并将其转换为对应的JSON字符串。这种序列化过程是为了将Java对象的数据结构转换为JSON格式,以便于在网络上传输或存储到文件中。

这种序列化过程与Java标准的序列化机制(实现 Serializable 接口)有所不同。Java标准的序列化机制是将对象转换为字节流,以便在Java环境中进行传输或持久化。而Jackson的序列化是将对象转换为JSON字符串,用于在不同系统之间传输数据。

因此,虽然Jackson的序列化过程确实涉及将Java对象转换为另一种格式(JSON字符串),但它并不使用Java标准的序列化机制。Jackson库是专门用于处理JSON数据的,它提供了一种更灵活和定制化的方式来转换Java对象和JSON数据之间的关系。

IDEA 设置创建类自动生成Serializable接口与serialVersionUID

特殊说明:
上述文章均是作者实际操作后产出。烦请各位,请勿直接盗用!转载记得标注原文链接:www.zanglikun.com
第三方平台不会及时更新本文最新内容。如果发现本文资料不全,可访问本人的Java博客搜索:标题关键字。以获取全部资料 ❤