Java的POJO类为什么要实现Serializable接口

简介: Java的POJO类为什么要实现Serializable接口

遇到过的问题

在分布式架构中,项目结构一般会把公用的部分抽取到一个单独的项目中,比如,与数据库映射的类 User 放到 xx-core 工程中,对 User 的操作(CRUD)封装成一个服务,如 xx-service,在这个 service 中引入 xx-core 依赖,然后对外提供接口服务能力。下面看一下 User 类的代码:


public class User {
    private String name;    private Integer age;
    // getter and setter    // ...
    @Override    public String toString() {        return "User{" +                "name='" + name + '\'' +                ", age=" + age +                '}';    }}

问题一

xx-service 项目中进行单元测试,没有问题,一旦另一个项目通过网络(RPC或 HTTP)远程调用 xx-service 中的接口,就会报序列化异常,通过日志很快能发现 User 类没有实现 Serializable 接口,修正代码如下:


public class User implements Serializable {
    private String name;    private Integer age;
    // getter and setter    // ...
    @Override    public String toString() {        return "User{" +                "name='" + name + '\'' +                ", age=" + age +                '}';    }}

再次调用一切正常。

问题二

随着需求的迭代,需要对 User 类进行字段扩展以满足新的需求,如对 User 增加手机号字段,代码如下:



public class User implements Serializable {
    private String name;    private Integer age;    // 新增字段    private String phone;
    // getter and setter    // ...
    @Override    public String toString() {        return "User{" +                "name='" + name + '\'' +                ", age=" + age +                '}';    }}

新服务 xx-service 发布上线后,发布有部分依赖 xx-core 工程的报出


Exception in thread "main" java.io.InvalidClassException: com.xxx.User; local class incompatible: stream classdesc serialVersionUID = 1035612825366362028, local class serialVersionUID = -1830850955695931978

报错原因为序列化与反序列化产生的 serialVersionUID 不一致,接下来在上面 User 类的基础上显示指定一个 serialVersionUID 问题解决,利用开发工具自动生成。如果在使用过程中修改这个 serialVersionUID 的值也会报同样的异常,所以请不要随意修改这个字段。修改后的代码如下:



public class User implements Serializable {
    // 不用关心具体的数字,也不要随意修改    private static final long serialVersionUID = 1035612825366362028L;
    private String name;    private Integer age;    private String phone;
    // getter and setter    // ...
    @Override    public String toString() {        return "User{" +                "name='" + name + '\'' +                ", age=" + age +                '}';    }}

在阿里 Java 开发手册也有这样的强制规范

【强制】序列化类新增属性时,请不要修改 serialVersionUID 字段,避免反序列失败; 如果完全不兼容升级,避免反序列化混乱,那么请修改 serialVersionUID 值。说明:注意 serialVersionUID 不一致会抛出序列化运行时异常。

总结

什么是序列化和反序列化?

序列化:把对象转换为字节序列的过程称为对象的序列化。反序列化:把字节序列恢复为对象的过程称为对象的反序列化。

什么时候需要用到序列化和反序列化呢?

当我们只在本地 JVM 里运行下 Java 实例,这个时候是不需要序列化和反序列化的,但当我们需要将内存中的对象持久化到磁盘, 数据库中时, 当我们需要与浏览器进行交互时, 当我们需要实现 RPC 时, 这个时候就需要序列化和反序列化了。

总之,只要我们对内存中的对象进行持久化或网络传输,这个时候都需要序列化和反序列化。

为什么还要显示指定 serialVersionUID 的值?

如果不显示指定 serialVersionUID,JVM 在序列化时会根据属性自动生成一个 serialVersionUID,然后与属性一起序列化,再进行持久化或网络传输。在反序列化时,JVM 会再根据属性自动生成一个新版serialVersionUID, 然后将这个新版 serialVersionUID 与序列化时生成的旧版 serialVersionUID 进行比较,如果相同则反序列化成功,否则报错。

如果显示指定了 serialVersionUID, JVM 在序列化和反序列化时仍然都会生成一个 serialVersionUID,但值为我们显示指定的值,这样在反序列化时新旧版本的 serialVersionUID 就一致了.

在实际开发中,我们的类会不断迭代,一旦类被修改了,那旧对象反序列化就会报错。所以在实际开发中,我们都会显示指定一个serialVersionUID

Java序列化的其他特性

transientstatic 关键字修饰的属性不会被序列化。

最佳实践

在定义 POJO 类时都实现 Serializable 接口,并且生成 serialVersionUID 属性,不管值是什么,都不要进行修改。

推荐阅读

Java的BigDecimal里方法应该这样用

干掉 try catch !

别再用if-else了,用注解去代替他吧

如果觉得还有帮助的话,你的关注和转发是对我最大的支持,O(∩_∩)O:

相关文章
|
1月前
|
安全 Java 数据建模
Java记录类:简化数据载体的新选择
Java记录类:简化数据载体的新选择
177 101
|
6天前
|
存储 Java 索引
用Java语言实现一个自定义的ArrayList类
自定义MyArrayList类模拟Java ArrayList核心功能,支持泛型、动态扩容(1.5倍)、增删改查及越界检查,底层用Object数组实现,适合学习动态数组原理。
52 4
|
11天前
|
IDE JavaScript Java
在Java 11中,如何处理被弃用的类或接口?
在Java 11中,如何处理被弃用的类或接口?
102 5
|
14天前
|
JSON 网络协议 安全
【Java】(10)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
65 1
|
14天前
|
Java Go 开发工具
【Java】(9)抽象类、接口、内部的运用与作用分析,枚举类型的使用
抽象类必须使用abstract修饰符来修饰,抽象方法也必须使用abstract修饰符来修饰,抽象方法不能有方法体。抽象类不能被实例化,无法使用new关键字来调用抽象类的构造器创建抽象类的实例。抽象类可以包含成员变量、方法(普通方法和抽象方法都可以)、构造器、初始化块、内部类(接 口、枚举)5种成分。抽象类的构造器不能用于创建实例,主要是用于被其子类调用。抽象类中不一定包含抽象方法,但是有抽象方法的类必定是抽象类abstract static不能同时修饰一个方法。
149 1
|
14天前
|
Java Go 开发工具
【Java】(8)正则表达式的使用与常用类分享
正则表达式定义了字符串的模式。正则表达式并不仅限于某一种语言,但是在每种语言中有细微的差别。
126 1
|
14天前
|
存储 Java 程序员
【Java】(6)全方面带你了解Java里的日期与时间内容,介绍 Calendar、GregorianCalendar、Date类
java.util 包提供了 Date 类来封装当前的日期和时间。Date 类提供两个构造函数来实例化 Date 对象。第一个构造函数使用当前日期和时间来初始化对象。Date( )第二个构造函数接收一个参数,该参数是从1970年1月1日起的毫秒数。
94 1
|
14天前
|
JSON 网络协议 安全
【Java基础】(1)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
58 1
|
24天前
|
算法 安全 Java
除了类,Java中的接口和方法也可以使用泛型吗?
除了类,Java中的接口和方法也可以使用泛型吗?
78 11
|
20天前
|
编解码 Java 开发者
Java String类的关键方法总结
以上总结了Java `String` 类最常见和重要功能性方法。每种操作都对应着日常编程任务,并且理解每种操作如何影响及处理 `Strings` 对于任何使用 Java 的开发者来说都至关重要。
165 5