我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Java内置接口Serializable示例详解

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Java内置接口Serializable示例详解

引言

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

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

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

本文大纲如下:

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 序列化与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 内置接口,请关注编程网其它相关文章!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Java内置接口Serializable示例详解

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Java内置接口Serializable示例详解

这篇文章主要为大家介绍了Java内置接口Serializable示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2022-11-13

java中Serializable接口作用详解

本文为大家解析java中Serializable接口的作用,具体内容如下 1.(serializable)主要支持对象的回复,所以可以用来保存当前的程序系统状态,远程方法调用RMI(远程机器必须含有必要的.class文件,否则将掷出clas
2023-05-31

java中的interface接口实例详解

java中的interface接口实例详解接口:Java接口是一些方法表征的集合,但是却不会在接口里实现具体的方法。java接口的特点如下:1、java接口不能被实例化2、java接口中声明的成员自动被设置为public,所以不存在pri
2023-05-31

Future与FutureTask接口实现示例详解

这篇文章主要为大家介绍了Future与FutureTask接口实现示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2022-11-13

Vue 实现接口进度条示例详解

这篇文章主要介绍了Vue实现接口进度条功能,在请求数据的过程中,需要添加监听函数来监测数据请求的过程变化,并更新组件相应的属性和界面元素,本文结合实例代码详细讲解,需要的朋友可以参考下
2023-05-17

Python+flask实现restful接口的示例详解

这篇文章主要为大家详细介绍了Python如何利用flask实现restful接口,文中的示例代码讲解详细,具有一定的借鉴价值,需要的可以参考一下
2023-02-08

Go语言中io包核心接口示例详解

目录前言ReaderWriterCloserSeeker组合接口总结前言 IO 操作是我们在编程中不可避免会遇到的,例如读写文件,Go语言的 io 包中提供了相关的接口,定义了相应的规范,不同的数据类型可以根据规范去实现相应的方法,提供更加
2022-06-07

vue 内置组件 component 的用法示例详解

这篇文章主要介绍了vue内置组件component的用法,本文给大家介绍了component内置组件切换方法,通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
2022-11-13

Elasticsearch Analyzer 内置分词器使用示例详解

这篇文章主要为大家介绍了Elasticsearch Analyzer 内置分词器使用示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2022-11-13

Java源码解析之接口Collection的示例分析

小编给大家分享一下Java源码解析之接口Collection的示例分析,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!一、图示二、方法定义我们先想一想,公司如果要我
2023-06-15

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录