前言

原本实现Serializable接口的时候一直都没有serialVersionUID属性,直到看到涉及MybatisPlus新项目中都有该属性,于是做了一期学习了解,最后发现该属性类似深度学习训练中的种子seed,类似版本控制!

1. 概念

serialVersionUID 是 Java 中用于版本控制序列化的一个字段。

手动方式默认值为1L,比如:private static final long serialVersionUID = 1L

  • 一个长整型数值
  • 是一个 private static final long 类型的字段,用于标识一个类的序列化版本号
  • 在序列化和反序列化过程中,serialVersionUID 被用来验证序列化对象和反序列化对象的类是否兼容,即是否是同一个类的不同版本。

类似的代码如下:

import java.io.Serializable;

public class MyClass implements Serializable {
    private static final long serialVersionUID = 1L;
    // other class members and methods
}

在这个示例中,MyClass 类显式地指定了 serialVersionUID 为 1L,以确保在类的结构发生变化时能够更好地进行版本控制

对于Serializable 类,其源码只是一个接口:public interface Serializable { }

总的来说:

serialVersionUID 是根据类的结构计算得到的哈希值,通常为负数。这个值是通过对类的结构进行哈希运算而生成的,因此不同版本的类将具有不同的 serialVersionUID。其存在的主要目的是为了处理序列化和反序列化过程中的版本兼容性问题。

当一个类被序列化后,其字节表示可能会存储在磁盘上或通过网络传输到不同的 JVM(Java 虚拟机)。如果类的结构发生了变化,例如添加了新的字段或方法,反序列化时就可能出现版本不一致的问题。为了解决这个问题,引入了 serialVersionUID 的概念。

  • 版本兼容性
    serialVersionUID 的存在是为了确保序列化和反序列化的版本兼容性。
    当类的结构发生变化时,不同版本的类将具有不同的 serialVersionUID。
    在序列化和反序列化过程中,Java 使用 serialVersionUID 来验证类的版本是否一致,以防止版本不匹配引发的问题。

  • 自动生成和手动生成:(上述已阐述)

  • 兼容性处理:
    当反序列化旧版本的对象时,如果新版本的类中删除了某些字段或方法,Java 虚拟机会忽略这些字段或方法,而不会引发异常。
    这种处理方式允许在一定程度上保持不同版本的类的兼容性,使得应用程序在进行升级时更加灵活。

总体而言,serialVersionUID 是一个重要的概念,用于确保序列化和反序列化过程中类的版本兼容性,同时提供了灵活的手动生成方式,使开发人员能够更好地控制类的版本信息。

2. Demo

为了更加方便的显示serialVersionUID的作用,可看下方的例子

原本Entity中有一个实体类,实现了Serializable 类

@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("test_user")
public class User implements Serializable {
    private static final long serialVersionUID = 1L;

    @TableId(value = "id", type = IdType.AUTO)
    private int id;
    private String username;
    private String password;
    // 其他字段...

}

通过序列化User的对象,并再次反序列化读取对象:

import java.io.*;

public class SerializationDemo {
    // 序列化对象
    public static void serializeUser(User user, String filename) throws IOException {
        try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(filename))) {
            out.writeObject(user);
        }
    }

    // 反序列化读取对象
    public static User deserializeUser(String filename) throws IOException, ClassNotFoundException {
        try (ObjectInputStream in = new ObjectInputStream(new FileInputStream(filename))) {
            return (User) in.readObject();
        }
    }

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        // 创建一个 User 对象并序列化保存
        User user = new User(123,"manong", "123");
        serializeUser(user, "user.ser");

        // 反序列化读取 Person 对象
        User deserializedPerson = deserializeUser("user.ser");
        System.out.println("Deserialized User: " + deserializedPerson);
    }
}

执行得到的结果为:Deserialized User: User(id=123, username=manong, password=123)

截图如下:

详解Java中的serialVersionUID概念以及作用(附上Demo)-LMLPHP


如果在此时修改版本(假设开发者不知道其版本),并且增加属性:

@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("test_user")
public class User implements Serializable {
	// 修改位置
    private static final long serialVersionUID = 2L;

    @TableId(value = "id", type = IdType.AUTO)
    private int id;
    private String username;
    private String password;
    // 其他字段...

	private String ceshi;


}

对应反序列化改为如下:

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        // 创建一个 User 对象并序列化保存
//        User user = new User(123,"manong", "123");
//        serializeUser(user, "user.ser");

        try {
            // 反序列化读取 Person 对象
            User deserializedPerson = deserializeUser("user.ser");
            System.out.println("Deserialized User: " + deserializedPerson);
        }
        catch (IOException | ClassNotFoundException e) {
            System.err.println("Error deserializing: " + e.getMessage());
        }
    }

根绝提示bug,也可看出不匹配:

Error deserializing: com.example.demo.entity.User; local class incompatible: stream classdesc serialVersionUID = 1, local class serialVersionUID = 2

截图如下:

详解Java中的serialVersionUID概念以及作用(附上Demo)-LMLPHP

01-09 08:20