java中那些绕不开的内置接口--serializable

author author     2022-12-05     562

关键词:

上一部分我们着重讲了 Java 集合框架中在开发项目时经常会被用到的数据容器,在讲解、演示使用实践的同时,把这个过程中遇到的各种相关知识点:泛型、​​Lambada​​​、​​Stream​​ 操作,一并给大家做了梳理。

从这篇开始我们进入下一部分,用三到五部分给大家梳理一下,在用 Java 编程时,那些我们绕不开的 ​​interface​​;从最基本的 ​​Serializable​​ 到 ​​Comparable​​ 和 ​​Iterator​​ 这些,再到 Java 为了支持函数式编程而提供的 ​​Function​​、​​Predicate​​ 等 ​​interface​​。

这些 Java 内置提供的 interface 或多或少我们在写 Java 代码的时候都见过,有的甚至是潜移默化地在日常编码中已经实现过其中的一些 interface,只不过我们没有察觉到罢了。相信通过阅读着几篇文章,一定会让你在写 Java 代码时更清楚自己是在做什么,不会再被这些个似曾相识的 interface 困扰到。

本文大纲如下:

Java

Serializable 接口

作为 Java 中那些绕不开的内置接口 这个小系列的开篇文章,首先要给大家介绍的 interface 是 ​​Serializable​​。

​Serializable​​这个接口的全限定名(包名 + 接口名)是 ​​java.io.Serializable​​,这里给大家说个小技巧,当你看到一个类或者接口的包名前缀里包含​​java.io​​那就证明这个类 / 接口它跟数据的传输有关。

​Serializable​​ 是 Java 中非常重要的一个接口,如果一个类的对象是可序列化的,即对象在程序里可以进行序列化和反序列化,对象的类就一定要实现​​Serializable​​接口。那么为什么要进行序列化和反序列化呢?

序列化的意思是将对象的状态转换为字节流;反序列化则相反。换句话说,序列化是将 Java 对象转换为静态字节流(序列),然后我们可以将其保存到文件、数据库或者是通过通过网络传输,反序列化则是在我们读取到字节流后再转换成 Java 对象的过程;这也正好解释了为什么​​Serializable​​ 接口会归属到​​java.io​​包下面。

Serializable 是一个标记型接口

虽说需要进行序列化的对象,它们的类都需要实现 ​​Serializable​​ 接口,但其实你会发现,我们在让一个类实现 ​​Serializable​​ 接口时,并没有额外实现过什么抽线方法。

import java.io.Serializable;

public class Person implements Serializable
private String name;
private int age;

比如向上面个类文件里的内容,Person 类声明实现 Serializable 接口后,并没有去实现什么抽象方法,IDE 也不会用红线警告提示我们:“你有一个抽象方法需要实现” ,原因是 Serializable 接口里并没有声明抽象方法。

public interface Serializable 

这种不包含任何方法的 interface 被称为标记型接口,类实现 ​​Serializable​​接口不必实现任何特定方法,它只起标记作用,让 Java 知道该类可以用于对象序列化。

serializable Version UID

虽说一个类实现了 ​​Serializable​​ 接口的时候不需要实现特定的方法,但是经常会看到一些实现了​​Serializable​​的类中,都有一个名为​​serialVersionUID​​类型为​​long​​的私有静态 属性。

import java.io.Serializable;

public static class Person implements Serializable

private static final long serialVersionUID = -7792628363939354385L;

public String name;
public int age;

该属性修饰符里使用了​​final​​即赋值后不可更改。Java 的对象序列化 API 在从读取到的字节序列中反序列化出对象时,使用 serialVersionUID 这个静态类属性来判断:是否序列化对象时使用了当前相同版本的类进行的序列化。Java 使用它来验证保存和加载的对象是否具有相同的属性,确保在序列化上是兼容的。

大多数的 IDE 都可以自动生成这个 ​​serialVersionUID​​静态属性的值,规则是基于类名、属性和相关的访问修饰符。任何更改都会导致不同的数字,并可能导致 InvalidClassException。 如果一个实现 ​​Serializable​​ 的类没有声明 ​​serialVersionUID​​,JVM 会在运行时自动生成一个。但是,强烈建议每个可序列化类都声明 serialVersionUID,因为默认生成的​​serialVersionUID​​依赖于编译器,因此可能会导致意外的​​InvalidClassExceptions​​。

我上面那个例子里,​​Person​​ 类的​​serialVersionUID​​是用 Intelij IDEA 自动生成的,所以值看起来一大串,不是我自己些的。IDEA 默认不会给可序列化类自动生成 ​​serialVersionUID​​ 需要安装一个插件。

Java

这里给大家放一个截图,插件的安装和使用,网上有很多例子,大家需要的话动手搜一下,这里就不再占用太多篇幅讲怎么安装和使用这个插件了。

Java 序列化与JSON序列化的区别

Java 的序列化与现在互联网上 Web 应用交互数据常用的 JSON 序列化并不是一回事儿,这是咱们需要注意的,像 Java、C#、PHP 这些编程语言,都有自己的序列化机制把自家的对象序列化成字节然后进行传输或者保存,但是这些语言的序列化机制之间并不能互认,即用 Java 把对象序列化成字节、通过网络 RESTful API 传给一个 PHP 开发的服务,PHP 是没办法反序列化还原出这个对象的。这样才有了 JSON、XML、Protocol Buffer 这样的更通用的序列化标准。

例如在实际项目开发的时候,Java 对象往往被序列化为 JSON、XML 后再在网络上传输,如果对数据大小敏感的场景,会把 Java 对象序列化成空间占用更小的一些二进制格式,比如 Protocol Buffer ( 分布式 RPC 框架 gRPC 的数据交换格式)。这样做的好处是序列化后的数据可以被非 Java 应用程序读取和反序列化,例如,在 Web 浏览器中运行的 JavaScript 可以在本地将对象序列化成 JSON 传输给 Java 写的 API 接口,也可以从 Java API接口返回响应中的 JSON 数据,反序列化成 JavaScript 本地的对象 。

像上面列举的这些对象序列化机制,是不需要我们的 Java 类实现 Serializable 接口的。这些 JSON、XML 等格式的序列化类,通常使用 Java 反射来检查类,配合一些特定的注解完成序列化。

Java序列化相较于 JSON 的优势

上面介绍了 JSON 这样的通用序列化格式的优势,有的可能会问了,那还用 Java 序列化干啥。这里再给大家分析一下,Java 对象序列化虽然在通用性上不如 JSON 那些序列化格式,但是在 Java 生态内部却是十分好用的,其最聪明的一点是,它不仅能保存对象的副本,而且还会跟着对象里面的reference,把它所引用的对象也保存起来,然后再继续跟踪那些对象的reference,以此类推。

这个机制所涵盖的范围不仅包括对象的成员数据,而且还包含数组里面的reference。如果你要自己实现对象序列化的话,那么编写跟踪这些链接的程序将会是一件非常痛苦的任务。但是,Java的对象序列化就能精确无误地做到这一点,毫无疑问,它的遍历算法是做过优化的。

另外你们在一些资料里看过 Java Bean 的定义

1、所有属性为private
2、提供默认构造方法
3、提供getter和setter
4、实现java.io.Serializable接口

那么问题来了,为什么要进行序列化?每个实体bean都必须实现serializabel接口吗?以及我做项目的时候,没有实现序列化,同样没什么影响,到底什么时候应该进行序列化操作呢?

这里转载一个网上大佬对这个问题的解释

首先第一个问题,实现序列化的两个原因:

1、将对象的状态保存在存储媒体中以便可以在以后重新创建出完全相同的副本;

2、按值将对象从一个应用程序域发送至另一个应用程序域。实现serializabel接口的作用是就是可以把对象存到字节流,然后可以恢复,所以你想如果你的对象没实现序列化怎么才能进行持久化和网络传输呢,要持久化和网络传输就得转为字节流,所以在分布式应用中及设计数据持久化的场景中,你就得实现序列化。

第二个问题,是不是每个实体bean都要实现序列化,答案其实还要回归到第一个问题,那就是你的bean是否需要持久化存储媒体中以及是否需要传输给另一个应用,没有的话就不需要,例如我们利用fastjson将实体类转化成json字符串时,并不涉及到转化为字节流,所以其实跟序列化没有关系。

第三个问题,有的时候并没有实现序列化,依然可以持久化到数据库。这个其实我们可以看看实体类中常用的数据类型,例如Date、String等等,它们已经实现了序列化,而一些基本类型,数据库里面有与之对应的数据结构,从我们的类声明来看,我们没有实现serializabel接口,其实是在声明的各个不同变量的时候,由具体的数据类型帮助我们实现了序列化操作。

另外需要注意的是,在NoSql数据库中,并没有与我们Java基本类型对应的数据结构,所以在往nosql数据库中存储时,我们就必须将对象进行序列化,同时在网络传输中我们要注意到两个应用中javabean的serialVersionUID要保持一致,不然就不能正常的进行反序列化。

Java 类对象的序列化代码演示

到这里 Serializable 需要了解的基础知识就都给大家梳理出来了,这块属于选读,用 Java 编程写序列化代码的场景并不是太多,不过有兴趣就再接着往下看吧,有个印象,这样以后写代码的时候,哪天用上了,还能快速想起来在哪看过,再回来翻看。

Java 对象序列化(写入)由 ObjectOutputStream 完成,反序列化(读取)由 ObjectInputStream 完成。ObjectInputStream 和 ObjectOutputStream 是分别继承了 java.io.InputStream 和 java.io.OutputStream 抽象的实体类。 ObjectOutputStream 可以将对象的原型作为字节流写入 OutputStream。然后我们可以使用 ObjectInputStream 读取这些流。 ObjectOutputStream 中最重要的方法是:

public final void writeObject(Object o) throws IOException;

这个方法接收一个可序列化对象(实现了 Serializable 接口的类的对象)并将其转换为字节序列。同样,在ObjectInputStream 中最重要的方法是:

public final Object readObject() throws IOException, ClassNotFoundException;

此方法可以读取字节流并将其转换回 Java 对象。然后我们可以再使用类型转换(Type Cast)将其转换回原始的类型对象。

下面我们使用文章示例里的​​Person​​类再给大家演示一下 Java 的序列化代码。

public class Person implements Serializable 
private static final long serialVersionUID = 1L;
static String country = "ITALY";
private int age;
private String name;
transient int height;

// 省略 getter 和 setter

这里要注意一下, static 修饰的静态属性是类属性,并不属于对象,所以在序列化对象时不会把类中的静态属性序列化了,另外我们也可以使用 ​​transient​​关键字修饰那些我们想在序列化过程中忽略调的对象属性。

@Test 
public void serializingAndDeserializing_ThenObjectIsTheSame() ()
throws IOException, ClassNotFoundException
Person person = new Person();
person.setAge(20);
person.setName("Joe");

// 用指定文件路径--当前目录的 test_serialization.txt 文件创建 FileOutputStream。
// 在写入 FileOutputStream 时, FileOutputStream 会在在项目目录中创建文件
// “test_serialization.txt”
FileOutputStream fileOutputStream
= new FileOutputStream("./test_serialization.txt");
// 以 FileOutputStream 为底层输出流创建对象输出流 ObjectOutputStream
ObjectOutputStream objectOutputStream
= new ObjectOutputStream(fileOutputStream);
// 向 ObjectOutputStream 中写入 person 对象
objectOutputStream.writeObject(person);
// 把数据从流中刷到磁盘上
objectOutputStream.flush();
objectOutputStream.close();

// 用上面的文件路径,创建文件输入流
FileInputStream fileInputStream
= new FileInputStream("./test_serialization.txt");
// 以文件输入流创建对象输入流 ObjectInputStream
ObjectInputStream objectInputStream
= new ObjectInputStream(fileInputStream);
// 用对象输入流读取到文件中保存的序列化对象,反序列化成 Java Object 再转换成 Person 对象
Person p2 = (Person) objectInputStream.readObject();
objectInputStream.close();

assertTrue(p2.getAge() == person.getAge());
assertTrue(p2.getName().equals(person.getName()));

上面这个单元测试里的代码演示了,怎么把 Person 类的对象进行 Java 序列化保存到文件中,再从文件中读取对象被序列化后的字节序列,然后还原成​​Person​​类的对象。

因为我们的专栏还没有设计到 Java IO 这块的内容,所以各种输入输出流就不过多进行讲解了,为了方便大家阅读时理解上面的程序,我在上面程序注释里已经详细注释了每一步完成的操作,这些输入输出流我们等到讲到 Java IO 体系的时候再详细进行讲解。

总结

今天给大家梳理了 Java Serializable 接口的一些必须要了解的知识,Serializable 接口在我们用 Java 编程的时候经常见,但是很多人并不了解它的作用,因为它的主要作用还是用于标记类是否是可序列化类,这样 Java 的 ObjectOutputStream 和 ObjectInputStream 才能对类的对象进行序列化和反序列化。

下一篇我们分享 Iterable 和 Iterator 这两个名字看起差不多的 Java 内置接口。

java程序员绕不开的10本书。。。

不可错过的Java书籍Top10TOP3TOP1TOP2OnJava作者:[美]布鲁斯·埃克尔;译者:陈德伟臧秀涛孙卓秦彬一本适合各个层次Java开发者反复翻阅的经典著作-布鲁斯•埃克尔又一力作-Java圣经,豆瓣9分经典书籍-内容全面,J... 查看详情

聊聊java中绕不开的synchronized关键字,纯干货!!(代码片段)

Synchronized关键字在Java的并发体系里也是非常重要的一个内容,首先比较常规的是知道它使用的方式,可以锁对象,可以锁代码块,也可以锁方法,看一个简单的demopublicclassSynchronizedDemopublicstaticvoidmain(String[]args... 查看详情

了解ssl证书从https开始开发者绕不开的“劫”

...年也逐步强制要求开发者使用HTTPS接入。HTTPS似乎是一个绕不开的“劫”,让不少开发者费心不已。为什么开发者绕不 查看详情

做机器人开发,你一定绕不开的模块!

Allspark机载电脑Allspark是阿木实验室为广大AI智能硬件开发者打造的一款微型边缘计算机。在设计之初就定义了尺寸小巧、重量轻、算力强、可靠、扩展性高的特点。Allspark机身采用铝合金新材料外壳设计,内置静音散热风扇&#... 查看详情

sdust小学期飞机大战讲解-2绕不开的mfc类(代码片段)

2020-07-28高考成绩陆续出炉了,群地位+FFFFFFFFFFFFFFFF。本章主要整理了一些一定需要使用的MFC类,并提供了微软官方文档的链接供参考。CDC类CDC类定义了设备上下文对象,使用户可以方便地在窗口绘制图形图像和格式化打印字符。... 查看详情

bert不完全手册4.绕不开的mask?xlnet&electra

基于随机tokenMASK是Bert能实现双向上下文信息编码的核心。但是MASK策略本身存在一些问题,包括预训练和下游迁移中MASK的不一致性,被MASKtoken之间的独立性假设,以及只MASK15%的token带来的训练低效问题等等~那MASK有这么多的问题... 查看详情

你绕不开的组件—锁,4个方面手撕锁的多种实现

你绕不开的组件—锁,4个方面手撕锁的多种实现|互斥锁的原理|自旋锁的原理|原子操作的汇编代码|CAS的实现专注于服务器后台开发,包括C/C++,Linux,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoD... 查看详情

matplotlib:初学者绕不开的库,详解50种常用可视化图表!(代码片段)

...次接触、基础能力最全的可视化库,也是很多初学者绕不开的可视化库。今天我整理了50个Matplotlib常用图表,这些在数据分析和可见化中非 查看详情

晶尚纯草说年终奖,一个工薪族绕不开的话题

岁末将至,各个大哥大姐也都闲不住了,网上各种年终奖品又开始肆虐,小编的眼睛已然是眼花缭乱了!几家欢乐几家愁,辛苦一年,员工最希望的当然是工作单位的认可。一份可观的年终奖不仅是物质上的奖励,更是一份巨大... 查看详情

c++后端绕不开的7个开源项目,每一个源码值得深入研究

c++后端绕不开的7个开源项目,每一个源码值得深入研究|单线程Redis|mysql|nginx|序列化protobuf|cjson|libevent专注于服务器后台开发,包括C/C++,Linux,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB&... 查看详情

图形美不胜收,25个可视化案例,matplotlib始终都是数据可视化绕不开的python库(代码片段)

Matplotlib是很多初学者绕不开的库,随着可视化工具库的蓬勃发展,再见Matplotlib的声音很多,但是我想说Matplotlib其实真的很强大。今天我给大家分享25个Matplotlib图的汇编,这些案例大家需要画图时可以拿来即用... 查看详情

c#中thread,task,async/await,iasyncresult的那些事儿!

说起异步,Thread,Task,async/await,IAsyncResult这些东西肯定是绕不开的,今天就来依次聊聊他们1.线程(Thread)多线程的意义在于一个应用程序中,有多个执行部分可以同时执行;对于比较耗时的操作(例如io,数据库操作),或者等... 查看详情

java实体对象为什么要实现serializable接口?

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

docker大热,还不了解dockerfile你就out啦~

...道理是一样的。今天我们就来聊一聊Dockerfile怎么写,那些指令到底是什么意思。一、先来看一个简单的Dockerfile#这个Dockerfile作用是打一个python3项目环境二、Dockerfile 查看详情

serializable接口

>官方解释:Serializable接口是启用其序列化功能的接口。实现java.io.Serializable接口的类是可序列化的。没有实现此接口的类将不能使它们的任意状态被序列化或逆序列化。>通俗地说:任何类型只要实现了Serializable接口,就可... 查看详情

并发编程

...该系列为并发编程,在几乎所有的编程语言中,并发始终是绕不开的坎,可以说学习一门编程语言,学好了并发就说明这门语言你学的还可以.所以接下来就让我们好好看看python的并发是怎么实现的吧.在学习并发之前,有必要学习一下... 查看详情

java对象为啥要实现serializable接口?

...砖,在不知道重复了多少次定义Java实体对象时“implementsSerializable”的C/V大法后,脑海中突然冒出一个思维(A):问了自己一句“Java实体对象为什么一定要实现Serializable接口呢?”,关于这个问题,脑海中的另一个思维(B)立马给出... 查看详情

goasm学习笔记之ppt版

...了汇编学习之路。要知道,作为一个严肃的Gopher,汇编是绕不开的。本着输出倒逼输入的原则在学习之后开始整理材料,发现材料不好写,主要原因是汇编太多太杂, 查看详情