首页 > 其他分享 >Serializable是什么,为什么要实现Serializable接口?

Serializable是什么,为什么要实现Serializable接口?

时间:2024-03-18 12:11:19浏览次数:23  
标签:为什么 java 对象 接口 序列化 Serializable serialVersionUID

什么是Serializable接口
什么是序列化?
为什么要序列化对象
什么情况下需要序列化?
为什么要定义serialversionUID变量
序列化的使用
关于serialVersionUID
定义实体类的时候会先定义一个BaseDomain类用来实现Serializable接口
image

什么是Serializable接口

一个对象序列化的接口,一个类只有实现了Serializable接口,它的对象才能被序列化。

Serializable是java.io包中定义的、用于实现Java类的序列化操作而提供的一个语义级别的接口。

Serializable序列化接口没有任何方法或者字段,只是用于标识可序列化的语义。

实现了Serializable接口的类可以被ObjectOutputStream转换为字节流,同时也可以通过ObjectInputStream再将其解析为对象。例如,我们可以将序列化对象写入文件后,再次从文件中读取它并反序列化成对象,也就是说,可以使用表示对象及其数据的类型信息和字节在内存中重新创建对象。

什么是序列化?

序列化是将对象状态转换为可保持或传输的格式的过程。与序列化相对的是反序列化,它将流转换为对象。这两个过程结合起来,可以轻松地存储和传输数据。

为什么要序列化对象

把对象转换为字节序列的过程称为对象的序列化

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

序列化对于面向对象的编程语言来说是非常重要的,因为无论什么编程语言,其底层涉及IO操作的部分还是由操作系统其帮其完成的,而底层IO操作都是以字节流的方式进行的,所以写操作都涉及将编程语言数据类型转换为字节流,而读操作则又涉及将字节流转化为编程语言类型的特定数据类型。

什么情况下需要序列化?

当我们需要把对象的状态信息通过网络进行传输,或者需要将对象的状态信息持久化,以便将来使用时都需要把对象进行序列化。

那为什么还要继承Serializable。那是存储对象在存储介质中,以便在下次使用的时候,可以很快捷的重建一个副本。

或许你会问,我在开发过程中,实体并没有实现序列化,但我同样可以将数据保存到mysql、Oracle数据库中,为什么非要序列化才能存储呢?

我们来看看Serializable到底是什么,跟进去看一下,我们发现Serializable接口里面竟然什么都没有,只是个空接口
image

一个接口里面什么内容都没有,我们可以将它理解成一个标识接口。

比如在课堂上有位学生遇到一个问题,于是举手向老师请教,这时老师帮他解答,那么这位学生的举手其实就是一个标识,自己解决不了问题请教老师帮忙解决。在Java中的这个Serializable接口其实是给jvm看的,通知jvm,我不对这个类做序列化了,你(jvm)帮我序列化就好了。

Serializable接口就是Java提供用来进行高效率的异地共享实例对象的机制,实现这个接口即可

为什么要定义serialversionUID变量

首先看一下接口里的说明:
image
可以发现如果我们不自定义serialversionUID,系统就会生成一个默认的serialversionUID

从注释中我们可以看到,它强烈建议我们自己定义一个serialversionUID,因为默认生成的serialversionUID对class极其敏感,在反序列化的时候很容易抛出InvalidClassException异常。

序列化的使用

下面我们可以通过例子来实现将序列化的对象存储到文件,然后再将其从文件中反序列化为对象,代码示例如下:

先定义一个序列化对象User:

点击查看代码
public class User implements Serializable { 
        private static final long serialVersionUID = 1L; 
     
        private String userId; 
        private String userName; 
     
        public User(String userId, String userName) { 
            this.userId = userId; 
            this.userName = userName; 
        } 
    } 
然后我们编写测试类,来对该对象进行读写操作,我们先测试将该对象写入一个文件:
点击查看代码
 public class SerializableTest { 
     
        /** 
         * 将User对象作为文本写入磁盘 
         */ 
        public static void writeObj() { 
            User user = new User("1001", "Joe"); 
            try { 
                ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("/Users/guanliyuan/user.txt")); 
                objectOutputStream.writeObject(user); 
                objectOutputStream.close(); 
            } catch (IOException e) { 
                e.printStackTrace(); 
            } 
        } 
     
        public static void main(String args[]) { 
            writeObj(); 
        } 
    } 

运行上述代码,我们就将User对象及其携带的数据写入了文本user.txt中。

接下来,我们继续编写测试代码,尝试将之前持久化写入user.txt文件的对象数据再次转化为Java对象,代码如下:

点击查看代码
public class SerializableTest {
    /**
     * 将类从文本中提取并赋值给内存中的类
     */
    public static void readObj() {
        try {
            ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("/Users/guanliyuan/user.txt"));
            try {
                Object object = objectInputStream.readObject();
                User user = (User) object;
                System.out.println(user);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    public static void main(String args[]) {
        readObj();
    }
}

通过反序列化操作,可以再次将持久化的对象字节流数据通过IO转化为Java对象,结果如下:

cn.hyp.serializable.User@6f496d9f
序列化与反序列化操作过程就是这么的简单。只需要将User写入到文件中,然后再从文件中进行恢复,恢复后得到的内容与之前完全一样,但是两者是不同的对象。

关于serialVersionUID

对于JVM来说,要进行持久化的类必须要有一个标记,只有持有这个标记JVM才允许类创建的对象可以通过其IO系统转换为字节数据,从而实现持久化,而这个标记就是Serializable接口。而在反序列化的过程中则需要使用serialVersionUID来确定由那个类来加载这个对象,所以我们在实现Serializable接口的时候,一般还会要去尽量显示地定义serialVersionUID。

这个serialVersionUID的详细的工作机制是:在序列化的时候系统将serialVersionUID写入到序列化的文件中去,当反序列化的时候系统会先去检测文件中的serialVersionUID是否跟当前的文件的serialVersionUID是否一致,如果一直反序列化不成功,就说明当前类跟序列化后的类发生了变化,比如是成员变量的数量或者是类型发生了变化,那么在反序列化时就会发生crash,并且回报出错误:

点击查看代码
java.io.InvalidClassException: User; local class incompatible: stream classdesc serialVersionUID = -1451587475819212328, local class serialVersionUID = -3946714849072033140at 
java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:699)at 
java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1885)at 
java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1751)at
java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2042)at 
java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1573)at 
java.io.ObjectInputStream.readObject(ObjectInputStream.java:431)at 
Main.readUser(Main.java:32)at Main.main(Main.java:10)

刚开始提到了,serialVersionUID要不要指定呢?如果不指定会出现什么样的后果?如果指定了以后后边的值又代表着什么意思呢?既然系统指定了这个字段,那么肯定是有它的作用的。

如果我们在序列化中没有显示地声明serialVersionUID,则序列化运行时将会根据该类的各个方面计算该类默认的serialVersionUID值。但是,Java官方强烈建议所有要序列化的类都显示地声明serialVersionUID字段,因为如果高度依赖于JVM默认生成serialVersionUID,可能会导致其与编译器的实现细节耦合,这样可能会导致在反序列化的过程中发生意外的InvalidClassException异常。因此,为了保证跨不同Java编译器实现的serialVersionUID值的一致,实现Serializable接口的必须显示地声明serialVersionUID字段。

此外serialVersionUID字段地声明要尽可能使用private关键字修饰,这是因为该字段的声明只适用于声明的类,该字段作为成员变量被子类继承是没有用处的!有个特殊的地方需要注意的是,数组类是不能显示地声明serialVersionUID的,因为它们始终具有默认计算的值,不过数组类反序列化过程中也是放弃了匹配serialVersionUID值的要求。

标签:为什么,java,对象,接口,序列化,Serializable,serialVersionUID
From: https://www.cnblogs.com/HYP-HYP/p/18080063

相关文章

  • 为什么ASP.NET Core的路由处理器可以使用一个任意类型的Delegate
    毫不夸张地说,路由是ASP.NETCore最为核心的部分。路由的本质就是注册一系列终结点(Endpoint),每个终结点可以视为“路由模式”和“请求处理器”的组合,它们分别用来“选择”和“处理”请求。请求处理器通过RequestDelegate来表示,但是当我们在进行路由编程的时候,却可以使用任意类型的De......
  • 问题分析 | 为什么主库Waiting for semi-sync ACK from slave会阻塞set global super_
    作者:卢文双资深数据库内核研发本文首发于2023-12-0321:33:21https://dbkernel.com问题描述为什么主库上有Waitingforsemi-syncACKfromslave的时候,执行setglobalsuper_read_only=ON会导致等待全局读锁?问题复现MySQL主从高可用集群,semi-sync超时无限大:setglob......
  • InstantiationAwareBeanPostProcessor 接口实现
    BeanPostProcessor结构图1code如下:packagecom.gientech.resolveBeforeInstantiation;publicclassBeforeInstantiation{publicvoiddoSomething(){System.out.println("dosomething......");}}packagecom.gientech.resolveBefor......
  • go语言请求http接口示例 并解析json
    本例请求了天气api接口对接流程注册一个账号,对接免费实况天气接口阅读接口文档http://tianqiapi.com/index/doc?version=day请求接口解析json开发流程创建一个json.go文件需要引入的包import( "encoding/json" "fmt" "io/ioutil" "net/http")定义Wea......
  • USB接口与端点描述符
    主机在第一次发送获取配置描述符的命令时,只会读取配置描述符的前九个字节,它包含了配置描述符以及所有从属的接口,端点(及其他特定类描述符)的总长度。获取其他长度不固定的描述符也是这样分成两步执行如果一个配置描述符不只支持一个接口描述符,并且每个接口描述符都有自己从属......
  • 为什么 HTTP3.0 使用 UDP 协议?
    HTTP2.0和TCP的关系HTTP2.0是2015年推出的,还是比较年轻的,其重要的二进制分帧协议、多路复用、头部压缩、服务端推送等重要优化使HTTP协议真正上了一个新台阶。像谷歌这种重要的公司并没有满足于此,而且想继续提升HTTP的性能,花最少的时间和资源获取极致体验。那肯定要问HTTP2.0......
  • Python模块百科_操作系统接口_os[四]
    Python模块百科_操作系统接口_os[四]os---多种操作系统接口【第一部分】一、相关模块1.1os.path文件路径1.2fileinput文件读取1.3tempfile临时文件和目录1.4shutil高级文件和目录1.5platform操作系统底层模块二、关于函数适用性的说明2.1与操作系统相同的......
  • Java学习七之类和接口
    抽象类抽象类概念在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。比如:在打印图形例子中,我们发现,父类Shape中的draw方法好像并没有什么实际工......
  • HAL库&标准库,为什么更重视HAL库
    工作上使用英飞凌的芯片,英飞凌也提供了的类似ST的HAL库的SDL库,年前以太网外设出了点问题,最后查出了是产品上英飞凌SDL库没有及时更新,bug没修复。和同事讨论了为什么各大芯片厂商都在搞类似HAL库的这种高度封装的库首先为客户省去了开发底层驱动工程师的费用第二点我觉得比较重......
  • ROS2 自学之接口
    一、什么是接口std_msgs/msg/Stringstd_msgs/msg/UInt32在学习话题的时候,我们就已经接触到了如上两个接口,这两个接口分别是对应了字符串类型和u_int32的接口,所谓接口就是ROS2中提前定义好的一种规范。类似于充电器接口,尽管不同厂家制造的充电器不同,但他们都统一执行一种规......