什么是序列化? 您需要通过示例解释的有关Java序列化的所有知识

在上一篇文章中,我们介绍了在Java中创建对象的5种不同方法 ,我解释了如何对序列化对象进行反序列化以创建新对象,并且在此博客中,我将详细讨论序列化和反序列化。

我们将以下面的Employee类对象为例进行说明

 // If we use Serializable interface, static and transient variables do not get serialize  Employee class implements Serializable { // This serialVersionUID field is necessary for Serializable as well as Externalizable to provide version control, // Compiler will provide this field if we do not provide it which might change if we modify the class structure of our class, and we will get InvalidClassException, // If we provide value to this field and do not change it, serialization-deserialization will not fail if we change our class structure. private static final long serialVersionUID = 2L; private final String firstName; // Serialization process do not invoke the constructor but it can assign values to final fields private transient String middleName; // transient variables will not be serialized, serialised object holds null private String lastName; private int age; private static String department; // static variables will not be serialized, serialised object holds null public Employee(String firstName, String middleName, String lastName, int age, String department) { this .firstName = firstName; this .middleName = middleName; this .lastName = lastName; this .age = age; Employee.department = department; validateAge(); } private void validateAge() { System.out.println( "Validating age." ); if (age < 18 || age > 70 ) { throw new IllegalArgumentException( "Not a valid age to create an employee" ); } } @Override public String toString() { return String.format( "Employee {firstName='%s', middleName='%s', lastName='%s', age='%s', department='%s'}" , firstName, middleName, lastName, age, department); } // Custom serialization logic, // This will allow us to have additional serialization logic on top of the default one eg encrypting object before serialization private void writeObject(ObjectOutputStream oos) throws IOException { System.out.println( "Custom serialization logic invoked." ); oos.defaultWriteObject(); // Calling the default serialization logic } // Custom deserialization logic // This will allow us to have additional deserialization logic on top of the default one eg decrypting object after deserialization private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { System.out.println( "Custom deserialization logic invoked." ); ois.defaultReadObject(); // Calling the default deserialization logic // Age validation is just an example but there might some scenario where we might need to write some custom deserialization logic validateAge(); }  } 

什么是序列化和反序列化

在Java中,我们创建了几个对象,这些对象会相应地存活和死亡,并且当JVM死亡时,每个对象肯定会死亡,但是有时我们可能想在多个JVM之间重用一个对象,或者可能希望通过网络将对象传输到另一台机器。

好吧, 序列化允许我们将对象的状态转换为字节流,然后可以将其保存到本地磁盘上的文件中,或者通过网络发送到任何其他计算机。 反序列化使我们可以逆转该过程,这意味着将序列化的字节流再次转换为对象。

简而言之,对象序列化是将对象的状态保存到字节序列中的过程, 反序列化是从这些字节中重建对象的过程。 通常,完整的过程称为序列化,但我认为最好将两者都分类为更清晰。

序列化过程与平台无关,可以在一个平台上反序列化在一个平台上序列化的对象。

要将对象序列化和反序列化为文件,我们需要调用ObjectOutputStream.writeObject()ObjectInputStream.readObject()如以下代码所示:

 public class SerializationExample { public static void main(String[] args) throws IOException, ClassNotFoundException { Employee empObj = new Employee( "Shanti" , "Prasad" , "Sharma" , 25 , "IT" ); System.out.println( "Object before serialization => " + empObj.toString()); // Serialization serialize(empObj); // Deserialization Employee deserialisedEmpObj = deserialize(); System.out.println( "Object after deserialization => " + deserialisedEmpObj.toString()); } // Serialization code static void serialize(Employee empObj) throws IOException { try (FileOutputStream fos = new FileOutputStream( "data.obj" ); ObjectOutputStream oos = new ObjectOutputStream(fos)) { oos.writeObject(empObj); } } // Deserialization code static Employee deserialize() throws IOException, ClassNotFoundException { try (FileInputStream fis = new FileInputStream( "data.obj" ); ObjectInputStream ois = new ObjectInputStream(fis)) { return (Employee) ois.readObject(); } }  } 

只有实现Serializable的类才能被序列化

类似于序列化中Java克隆的Cloneable接口,我们有一个标记接口Serializable,其作用类似于JVM的标志。 直接或通过其父级实现Serializable接口的任何类都可以序列化,而没有实现Serializable则不能进行序列化。

Java的默认序列化过程是完全递归的,因此,每当我们尝试序列化一个对象时,序列化过程都会尝试用我们的类( statictransient字段除外)序列化所有字段(原始和引用)。

当一个类实现Serializable接口时,其所有子类也都可以序列化。 但是,当一个对象引用另一个对象时,这些对象必须分别实现Serializable接口。 如果我们的类甚至具有对非Serializable类的单个引用,则JVM将抛出NotSerializableException

为什么Object不能实现Serializable?

现在出现一个问题,如果序列化是非常基本的功能,并且任何不能实现Serializable类都不能Serializable化,那么为什么Serializable不是由Object本身实现的呢?通过这种方式,我们所有的对象都可以默认序列化。

Object类未实现Serializable接口,因为我们可能不想序列化所有对象,例如,对线程进行序列化没有任何意义,因为在JVM中运行的线程将使用系统的内存,并将其持久化并尝试在JVM中运行毫无意义。

瞬态和静态字段不会序列化

如果我们要序列化一个对象但不想序列化某些特定字段,则可以将这些字段标记为
短暂的

所有静态字段都属于类而不是对象,并且序列化过程会序列化对象,因此无法序列化静态字段。

  1. 序列化并不关心字段的访问修饰符,例如private 。 所有非瞬态和非静态字段都被视为对象持久状态的一部分,并且可以序列化。
  2. 我们只能在构造函数中将值分配给final字段,而序列化过程不会调用任何构造函数,但仍可以将值分配给final字段。

什么是serialVersionUID,为什么要声明它?

假设我们有一个类,并且已将其对象序列化为磁盘上的文件,并且由于一些新要求,我们在类中添加/删除了一个字段。 现在,如果我们尝试反序列化已经序列化的对象,我们将得到InvalidClassException ,为什么?

我们之所以得到它,是因为默认情况下,JVM将版本号与每个可序列化的类相关联,以控制类的版本控制。 它用于验证序列化和反序列化的对象具有相同的属性,从而与反序列化兼容。 版本号保存在一个名为serialVersionUID的字段中。 如果可序列化的类未声明
serialVersionUID JVM将在运行时自动生成一个。

如果我们更改类结构,例如删除/添加字段,则版本号也会更改,并且根据JVM,我们的类与序列化对象的类版本不兼容。 这就是为什么我们会得到例外,但是如果您真的考虑过它,为什么应该仅仅因为我添加了一个字段就抛出该例外? 不能仅将字段设置为其默认值,然后下次将其写出吗?

是的,可以通过手动提供serialVersionUID字段并确保始终相同来完成此操作。 强烈建议每个可序列化的类声明其serialVersionUID因为生成的类是编译器相关的,因此可能导致意外的InvalidClassExceptions。

您可以使用JDK发行版随附的实用程序,称为
serialver以查看默认情况下该代码是什么(默认情况下只是对象的哈希码)。

使用writeObject和readObject方法自定义序列化和反序列化

JVM可以完全控制默认序列化过程中的对象序列化,但是使用默认序列化过程有很多缺点,其中包括:

  1. 它无法处理无法序列化的字段的序列化。
  2. 反序列化过程在创建对象时不会调用构造函数,因此它无法调用构造函数提供的初始化逻辑。

但是我们可以在Java类中覆盖此默认的序列化行为,并提供一些其他逻辑来增强正常过程。 这可以通过在我们要序列化的类中提供两个方法writeObjectreadObject来完成:

 // Custom serialization logic will allow us to have additional serialization logic on top of the default one eg encrypting object before serialization  private void writeObject(ObjectOutputStream oos) throws IOException { // Any Custom logic oos.defaultWriteObject(); // Calling the default serialization logic // Any Custom logic  }  // Custom deserialization logic will allow us to have additional deserialization logic on top of the default one eg decrypting object after deserialization  private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { // Any Custom logic ois.defaultReadObject(); // Calling the default deserialization logic // Any Custom logic  } 

将两个方法都声明为私有是必要的(公共方法将不起作用),因此除了JVM外,其他任何东西都看不到它们。 这也证明方法既不被继承也不被覆盖或重载。 JVM自动检查这些方法并在序列化/反序列化过程中调用它们。 JVM可以调用这些私有方法,但其他对象则不能,因此,类的完整性得以维护,序列化协议可以继续正常工作。

即使提供了那些专用的私有方法,通过调用ObjectOutputStream.writeObject()ObjectInputStream.readObject() ,对象序列化的工作方式也相同。

ObjectOutputStream.writeObject()ObjectInputStream.readObject()的调用将启动序列化协议。 首先,检查对象以确保其实现了Serializable ,然后检查该对象是否提供了这些私有方法中的任何一个。 如果提供了它们,则将流类作为参数传递给这些方法,从而使代码可以控制其用法。

我们可以调用ObjectOutputStream.defaultWriteObject()
这些方法中的ObjectInputStream.defaultReadObject()获得默认的序列化逻辑。 这些调用听起来很像-他们执行序列化对象的默认写入和读取操作,这很重要,因为我们没有替换正常的过程,而只是添加了它。

这些私有方法可用于您要在序列化过程中进行的任何自定义,例如,可以将加密添加到输出中,并将解密添加到输入中(请注意,字节以明文形式写入和读取,完全没有混淆)。 它们可能被用来向流中添加额外的数据,也许是公司的版本代码,其可能性实际上是无限的。

停止序列化和反序列化

假设我们有一个从其父级获得序列化功能的类,这意味着我们的类是从另一个实现Serializable类扩展的。

这意味着任何人都可以序列化和反序列化我们类的对象。 但是,如果我们不希望对类进行序列化或反序列化(例如,我们的类是单例)并且希望防止任何新对象的创建,记住反序列化过程会创建一个新对象 。

要停止类的序列化,我们可以再次使用上述私有方法抛出NotSerializableException 。 现在,任何对我们的对象进行序列化或反序列化的尝试都将始终导致引发异常。 并且由于这些方法被声明为private方法,因此没有人可以覆盖您的方法并进行更改。

 private void writeObject(ObjectOutputStream oos) throws IOException { throw new NotSerializableException( "Serialization is not supported on this object!" );  }  private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { throw new NotSerializableException( "Serialization is not supported on this object!" );  } 

但是,这违反了《里斯科夫换人原则》。 和
writeReplace和readResolve方法可用于实现类似行为的单例。 这些方法用于允许对象在ObjectStream中为其自身提供替代表示。 简单来说,readResolve可用于更改通过readObject方法反序列化的数据,而writeReplace可用于更改通过writeObject序列化的数据。

Java 序列化还可以用于深度克隆对象 。 Java克隆是Java社区中最有争议的话题,它的确有其缺点,但是在对象完全满足Java克隆的强制条件之前,它仍然是创建对象副本的最流行和最简单的方法。 我在3篇文章的Java克隆系列中详细介绍了克隆 ,其中包括Java克隆和克隆类型(浅和深)等文章, 并带有示例 , Java克隆–复制构造器与克隆 , Java克隆–甚至复制构造器都不是如果您想了解更多有关克隆的知识,请充分阅读它们。

结论

  1. 序列化是将对象的状态保存为字节序列的过程,然后可以将其存储在文件中或通过网络发送, 反序列化是从这些字节中重建对象的过程。
  2. 只有Serializable接口的子类可以序列化。
  3. 如果我们的类未实现Serializable接口,或者该类具有对非Serializable类的引用,则JVM将抛出NotSerializableException
  4. 所有transientstatic字段都不会序列化。
  5. serialVersionUID用于验证序列化和反序列化的对象具有相同的属性,从而与反序列化兼容。
  6. 我们应该在我们的类中创建一个serialVersionUID字段,这样,如果我们更改类结构(添加/删除字段),JVM将不会通过InvalidClassException 。 如果我们不提供它,那么JVM提供的类可能会随着类结构的改变而改变。
  7. 通过提供writeObjectreadObject方法的实现,我们可以覆盖Java类内部的默认序列化行为。
  8. 我们可以从writeObjectreadObject方法调用ObjectOutputStream.defaultWriteObject()ObjectInputStream.defaultReadObject以获得默认的序列化和反序列化逻辑。
  9. 如果我们不希望类被序列化或反序列化,则可以从writeObjectreadObject抛出NotSerializableException异常。

可以使用“可Externalizable接口进一步定制和增强Java序列化过程,我已经在“ 如何通过使用可外部化接口在Java中自定义序列化”中进行了解释。

我还写了一系列文章,解释了有效Java的项目编号74到78,它进一步讨论了如何增强Java序列化过程,请继续阅读,如果愿意的话。

您可以在此Github存储库中找到本文的完整源代码,请随时提供宝贵的反馈。

翻译自: https://www.javacodegeeks.com/2019/08/serialization-everything-java-serialization-explained.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/341485.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

CopyTranslator 翻译神器的安装与使用

版权声明&#xff1a;本文为博主原创文章&#xff0c;遵循 CC 4.0 BY-SA 版权协议&#xff0c;转载请附上原文出处链接和本声明。 本文链接&#xff1a;https://blog.csdn.net/tianweidadada/article/details/102641908

网络交换机功能介绍

交换机的主要功能包括物理编址、网络拓扑结构、错误校验、帧序列以及流控。目前交换机还具备了一些新的功能&#xff0c;如对VLAN&#xff08;虚拟局域网&#xff09;的支持、对链路汇聚的支持&#xff0c;甚至有的还具有防火墙的功能。 交换机除了能够连接同种类型的网络之外…

C/C++头文件与变量的声明和定义

版权声明&#xff1a;本文为博主转载文章&#xff0c;遵循 CC 4.0 BY-SA 版权协议&#xff0c;转载请附上原文出处链接和本声明。 原文链接&#xff1a;https://blog.csdn.net/mountzf/article/details/51767353 最近遇到了变量重复包含的问题&#xff0c;才发现自己有好多知…

工业以太网交换机的接口知识详解

工业交换机作为局域网节点连接的网络设备&#xff0c;它的接口类型是随着各种局域网和传输介质类型的发展而变化的&#xff0c;分析一下局域网的主要网络类型和传输介质发展过程&#xff0c;我们就不难发现各种工业交换机接口类型。接下来就由飞畅科技的小编来为大家详细介绍下…

C++应用过程中使用知识点

一 读代码中遇到虚函数,此处总结虚函数的用法 转载链接 https://blog.csdn.net/hackbuteer1/article/details/7558868 二 C语言枚举类型&#xff08;C语言enum用法&#xff09;详解 转载链接:http://c.biancheng.net/view/2034.html 三 C运算符重载 转载链接 https://www.…

HOW-TO:具有MySQL的JEE应用程序中具有集群功能的Quartz Scheduler

Quartz Scheduler是Java世界中最流行的调度库之一。 过去&#xff0c;我主要在Spring应用程序中使用Quartz。 最近&#xff0c;我一直在研究要在云中部署的JBoss 7.1.1上运行的JEE 6应用程序中的调度。 我考虑的一种选择是Quartz Scheduler&#xff0c;因为它提供了与数据库的集…

办公网络对工业交换机的功能要求

如今&#xff0c;随着社会的发展&#xff0c;很多公司对网络的要求越来越高&#xff0c;系统越来越复杂&#xff0c;很多老线路需要改造升级&#xff0c;对工业交换机的要求也越来越高。但是&#xff0c;很多企业并不懂如何改造升级。今天飞畅科技的小编就来为大家详细讲解下公…

Ubuntu文件上锁了,怎么打开???亲测有效

第一步&#xff1a;你需要用root权限进入你要开锁的那个文件的目录下 第二步&#xff1a;使用下面的命令&#xff1a; ps&#xff1a;abc是你的用户名字 models是你的目标解锁文件名字 sudo chown abc models

鸡肉和鸡蛋–测试前解决Spring属性

考虑一个负责进行远程调用和获取详细信息的服务类&#xff1a; ... public class CitiesService { private final WebClient.Builder webClientBuilder; private final String baseUrl; public CitiesService( WebClient.Builder webClientBuilder, Value ( "${cityservi…

工业交换机和工控交换机有什么区别?

众所周知&#xff0c;以太网交换机一般分为&#xff1a;商用(以太网)交换机、工业(以太网)交换机、家用(以太网)交换机&#xff0c;因为我们是专业的工业交换机厂家&#xff0c;在这里着重介绍下工业交换机。 工业交换机一般用在工业生产场合&#xff0c;通常外观和安装形式多…

Ubuntu16.04通过wine环境安装微信与QQ等软件

很多双系统用户平时开发项目时会在Ubuntu下进行开发,但是由于Ubuntu下缺少腾讯QQ和微信的官方客户端支持,需要使用微信或者QQ只能使用网页版本,但是有一些用户由于某些原因是无法通过网页登录微信的,会提示"为了你的帐号安全&#xff0c;此微信号不能登录网页微信。你可以…

工业型交换机相比普通交换机有哪些要求?

随着自动化技术逐渐成熟&#xff0c;并伴随工业以太网的应用和大中型工业控制的网络的创建&#xff0c;工业交换机的应用越来越广泛。工业型交换机与一般交换机对比有必须的差异&#xff0c;工业型交换机在整体规划上及其在电子器件的采用上&#xff0c;其抗压强度和可接受性层…

使用eclipse调试ns3配置说明

Tips&#xff1a;安装eclipse时注意选择C开发组件&#xff1b; &#xff08;环境配置参考&#xff1a;https://www.cnblogs.com/zlcxbb/p/3852810.html&#xff09; &#xff08;官方配置介绍&#xff1a;https://www.nsnam.org/wiki/HOWTO_configure_Eclipse_with_ns-3&…

工业以太网交换机特点分析及使用注意事项

在网络发达的今天&#xff0c;交换机被许多需要使用网络的朋友使用&#xff0c;可以实现一个网络多台电脑公用。但是什么是工业以太网交换机&#xff0c;很惯性的思维就是工业用的交换机&#xff0c;但是具体工业以太网交换机性能特点有什么呢&#xff1f;接下来就由杭州飞畅科…

java整数的因式分解_如何在Java中找到整数的质数-因式分解

java整数的因式分解编程课程中的常见家庭作业/任务之一是关于Prime Factorization。 要求您编写一个程序以找到给定整数的素因子 。 一个数字的素数因子是将精确地除以给定数字的所有素数。 例如&#xff0c;素数因子35是7和5&#xff0c;它们本身都是素数&#xff0c;并且精确…

【最新】解决Github网页上图片显示失败的问题

转载链接&#xff1a; https://blog.csdn.net/qq_38232598/article/details/91346392?utm_mediumdistribute.pc_relevant_t0.none-task-blog-BlogCommendFromBaidu-1.not_use_machine_learn_pai&depth_1-utm_sourcedistribute.pc_relevant_t0.none-task-blog-BlogCommend…

串口服务器的通讯模式

串口服务器&#xff0c;一个为RS-232/485/422到PC/IP之间完成数据转换的具有强大功能的方便快捷的通讯接口转换器。串口服务器通过作为服务器端&#xff0c;提供RS-232/485/422终端串口与TCP/IP网络的数据双向透明传输&#xff0c;提供串口转网络功能&#xff0c;RS-232/485/42…

JUnit 5和Selenium –使用Gradle,JUnit 5和Jupiter Selenium设置项目

Selenium是一组支持浏览器自动化的工具和库&#xff0c;主要用于Web应用程序测试。 Selenium的组件之一是Selenium WebDriver&#xff0c;它提供客户端库&#xff0c;JSON有线协议&#xff08;与浏览器驱动程序进行通信的协议&#xff09;和浏览器驱动程序。 Selenium WebDrive…

机器学习相关知识 大佬博客整理

一 马尔科夫链详细介绍 https://www.cnblogs.com/traditional/p/12612010.html

ns3gym与ns3ai的安装方法

编译运行的常用命令 1&#xff0c;针对ns3主项目的编译命令 分两步&#xff1a;第一&#xff0c;./waf configure&#xff1b;第二&#xff0c;./waf&#xff08;或者./waf build&#xff09; 详见《开源网络模拟器ns3》P13 2&#xff0c;针对多脚本同时运行的编译命令 ns3…