Serializable接口概述

Serializable是java.io包中定义的、用于实现Java类的序列化操作而提供的一个语义级别的接口。Serializable序列化接口没有任何方法或者字段,只是用于标识可序列化的语义。实现了Serializable接口的类可以被ObjectOutputStream转换为字节流,同时也可以通过ObjectInputStream再将其解析为对象。例如,我们可以将序列化对象写入文件后,再次从文件中读取它并反序列化成对象,也就是说,可以使用表示对象及其数据的类型信息和字节在内存中重新创建对象。

而这一点对于面向对象的编程语言来说是非常重要的,因为无论什么编程语言,其底层涉及IO操作的部分还是由操作系统其帮其完成的,而底层IO操作都是以字节流的方式进行的,所以写操作都涉及将编程语言数据类型转换为字节流,而读操作则又涉及将字节流转化为编程语言类型的特定数据类型。而Java作为一门面向对象的编程语言,对象作为其主要数据的类型载体,为了完成对象数据的读写操作,也就需要一种方式来让JVM知道在进行IO操作时如何将对象数据转换为字节流,以及如何将字节流数据转换为特定的对象,而Serializable接口就承担了这样一个角色。

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

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

import java.io.Serializable;

public class User implements Serializable {

    /**
*
*/
private static final long serialVersionUID = 7594992187048972885L; private String userId;
private String userName;
public User(String userId, String userName) {
super();
this.userId = userId;
this.userName = userName;
} }

然后我们编写测试类,来对该对象进行读写操作,我们先测试将该对象写入一个文件:

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream; public class SerializableTest { /**
* * 将User对象作为文本写入磁盘
*/
public static void writeObj() {
User user = new User("1001", "Joe");
try {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("D:/Users/user.txt"));
objectOutputStream.writeObject(user);
objectOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
} public static void main(String[] args) {
writeObj();
} }

运行上述代码,我们就将User对象及其携带的数据写入了文本user.txt中,我们可以看下user.txt中存储的数据此时是个什么格式:

我们看到对象数据以二进制文本的方式被持久化到了磁盘文件中。

在进行反序列化测试之前,我们可以尝试下将User实现Serializable接口的代码部分去掉,看看此时写操作是否还能成功,结果如下:

java.io.NotSerializableException: cn.wudimanong.serializable.User
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
at cn.wudimanong.serializable.SerializableTest.writeObj(SerializableTest.java:19)
at cn.wudimanong.serializable.SerializableTest.main(SerializableTest.java:27)

结果不出所料,果然是不可以的,抛出了NotSerializableException异常,提示非可序列化异常,也就是说没有实现Serializable接口的对象是无法通过IO操作持久化的。

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

import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream; public class SerializableTest { /**
** 将类从文本中提取并赋值给内存中的类
*/
public static void readObj() {
try {
ObjectInputStream objectInputStream = new ObjectInputStream(
new FileInputStream("D:/Users/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对象,结果如下:

此时,如果我们再次尝试将User实现Serializable接口的代码部分去掉,发现也无法再文本转换为序列化对象,报错信息为:

ava.io.InvalidClassException: cn.wudimanong.serializable.User; class invalid for deserialization
at java.io.ObjectStreamClass$ExceptionInfo.newInvalidClassException(ObjectStreamClass.java:157)
at java.io.ObjectStreamClass.checkDeserialize(ObjectStreamClass.java:862)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2038)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1568)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:428)
at cn.wudimanong.serializable.SerializableTest.readObj(SerializableTest.java:31)
at cn.wudimanong.serializable.SerializableTest.main(SerializableTest.java:44)

提示非法类型转换异常,说明在Java中如何要实现对象的IO读写操作,都必须实现Serializable接口,否则代码就会报错!

序列化&反序列化

通过上面的阐述和示例,相信大家对Serializable接口的作用是有了比较具体的体会了,接下来我们上层到理论层面,看下到底什么是序列化/反序列化。序列化是指把对象转换为字节序列的过程,我们称之为对象的序列化,就是把内存中的这些对象变成一连串的字节(bytes)描述的过程。

而反序列化则相反,就是把持久化的字节文件数据恢复为对象的过程。那么什么情况下需要序列化呢?大概有这样两类比较常见的场景:1)、需要把内存中的对象状态数据保存到一个文件或者数据库中的时候,这个场景是比较常见的,例如我们利用mybatis框架编写持久层insert对象数据到数据库中时;2)、网络通信时需要用套接字在网络中传送对象时,如我们使用RPC协议进行网络通信时;

关于serialVersionUID

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

private static final long serialVersionUID = 1L; 

在反序列化的过程中,如果接收方为对象加载了一个类,如果该对象的serialVersionUID与对应持久化时的类不同,那么反序列化的过程中将会导致InvalidClassException异常。例如,在之前反序列化的例子中,我们故意将User类的serialVersionUID改为2L,如:

private static final long serialVersionUID = 2L; 

那么此时,在反序例化时就会导致异常,如下:

java.io.InvalidClassException: cn.wudimanong.serializable.User; local class incompatible: stream classdesc serialVersionUID = 1, local class serialVersionUID = 2
at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:687)
at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1880)
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1746)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2037)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1568)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:428)
at cn.wudimanong.serializable.SerializableTest.readObj(SerializableTest.java:31)
at cn.wudimanong.serializable.SerializableTest.main(SerializableTest.java:44)

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

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

Java对象为啥要实现Serializable接口的更多相关文章

  1. Java实体对象为什么要实现Serializable接口?

    前言 Java实体对象为什么一定要实现Serializable接口呢?在学JavaSE的时候有些实体对象不实现Serializable不是也没什么影响吗? 最近在学习mybatis的时候发现,老师写的 ...

  2. 面试官:Java序列化为什么要实现Serializable接口?我懵了

    整理了一些Java方面的架构.面试资料(微服务.集群.分布式.中间件等),有需要的小伙伴可以关注公众号[程序员内点事],无套路自行领取 更多优选 一口气说出 9种 分布式ID生成方式,面试官有点懵了 ...

  3. 我写了一个java实体类,implements了Serializable接口,然后我如何让serialversionUID自动生成

    写了一个java实体类,implements了Serializable接口,让serialversionUID自动生成方法: 1.点击类旁边的警告符号: 2.选择Add generated seria ...

  4. 序列化、反序列化(实体类或要序列化的对象类必须实现Serializable接口)

    package com.phone.shuyinghengxie; import java.io.Serializable; /* 一个类的对象要想序列化成功,必须满足两个条件: 该类必须实现 jav ...

  5. Java中的实体类--Serializable接口、transient 关键字

    在java中,实体类是一个非常重要的概念,我们可以在实体类中封装对象.设置其属性和方法等.关于实体类,也经常涉及到适配器模式.装饰者模式等设计模式.那么在实际代码开发中,关于实体类的注意事项有哪些呢? ...

  6. java类为什么要实现Serializable接口

    什么是Serializable接口? 一个对象序列化的接口.一个类只有实现了Serializable接口,它的对象才能被序列化. 什么是序列化? 将对象的状态信息转换为可以存储或传输的形式的过程. 在 ...

  7. 【Java面试题】45 什么是java序列化,如何实现java序列化?或者请解释Serializable接口的作用。

    我们有时候将一个java对象变成字节流的形式传出去或者从一个字节流中恢复成一个java对象,例如,要将java对象存储到硬盘或者传送给网络上的其他计算机,这个过程我们可以自己写代码去把一个java对象 ...

  8. Java对象表示方式1:序列化、反序列化和transient关键字的作用

    平时我们在Java内存中的对象,是无法进行IO操作或者网络通信的,因为在进行IO操作或者网络通信的时候,人家根本不知道内存中的对象是个什么东西,因此必须将对象以某种方式表示出来,即存储对象中的状态.一 ...

  9. java 对象序列化 RMI

    对于一个存在于Java虚拟机中的对象来说,其内部的状态只保持在内存中.JVM停止之后,这些状态就丢失了.在很多情况下,对象的内部状态是需要被持久化下来的.提到持久化,最直接的做法是保存到文件系统或是数 ...

随机推荐

  1. (备忘)Java web项目迁移到Centos7中验证码无法显示

    每天多学一点知识. 今天部署项目的时候出现验证码无法显示的问题,如下图所示:

  2. 2013.4.29 - KDD第十一天

    今天上午在图书馆写FIrst集,真心没写出来,算法是昨天找好的,不过实现的话还是需要很大的代码量,然后就打算用郑茂或者韩冰的代码了. 晚上图书馆快关门的时候开始思考KDD的问题, 我一开始打算给中秋发 ...

  3. 关于ServletContext的私有方法全局获取返回null问题getServletContext().setAttribute("count", 1)

    1.在Servlet重写了init方法中获取getServletContext()报错提示为空指针 重写了init(ServletConfig)方法,但是重写的init方法内部没有调用super.in ...

  4. Python高级编程和异步IO并发编程(笔记)

    一.魔法函数 # 例子 class Company(object): def __init__(self, employee_list): self.employee = employee_list ...

  5. Hibernate框架的查询方式

    技术分析之Hibernate框架的查询方式        1. 唯一标识OID的检索方式        * session.get(对象.class,OID)    2. 对象的导航的方式       ...

  6. Tensorflow细节-P112-模型持久化

    第一个代码 import tensorflow as tf v1 = tf.Variable(tf.random_normal([1], stddev=1, seed=1)) v2 = tf.Vari ...

  7. qDeleteAll与clear

    qDeleteAll:专门用于指针容器,对容器或者迭代器中的每个对象进行delete操作,而不是从容器中移除对象.源代码如下: void qDeleteAll(ForwardIterator begi ...

  8. RookeyFrame 线上 添加Model

    线上添加好了模块,会在本地生成几个文件 类文件:Rookey.Frame.Web\Config\TempModel\Order_File.code DLL文件:Rookey.Frame.Web\bin ...

  9. RookeyFrame Bug 编号显示 系统自动生成 的问题,有时候依旧会显示text文本框

    编号显示 系统自动生成 的问题,有时候依旧会显示text文本框 1.在线新建model -> 启用编码规则 -> 新建字段Code(主键) 2.跟Code字段 创建编码规则 3.新增菜单 ...

  10. 关于Pi