在 Java 中,java.io.Serializable 接口属于标记接口。若要对某个类执行序列化与反序列化操作,那么该类就必须实现此接口。具体而言,Java 对象的序列化(也就是将对象写入流)操作是借助 ObjectOutputStream 来达成的;而反序列化(即从流中读取对象)操作则是通过 ObjectInputStream 来完成的。
Serializable 作为标记接口,其特点是不包含任何方法。所以,实现了 Serializable 接口的类无需实现特定的方法。实现 Serializable 接口的意义在于,向 Java 序列化机制表明这个类可用于对象的序列化操作。
序列化是将对象的状态转换为可存储或传输格式的过程。
序列化过程会将对象的属性、方法等状态信息进行提取,并按照特定的规则将其转化为字节流、文本格式(如 JSON、XML)或其他自定义的二进制格式。这样做的目的是为了让对象能够在不同的环境中进行存储、传输或共享。
数据持久化:将内存中的对象保存到磁盘或数据库等存储介质中,以便在需要时可以重新加载到内存中使用。例如,在开发应用程序时,可能需要将用户的配置信息、业务数据等对象进行持久化存储,以便下次应用程序启动时能够恢复这些对象的状态。
网络传输:在网络通信中,对象需要通过网络进行传输。由于网络传输只能处理字节流,因此需要将对象序列化为字节流形式,然后在接收端再将字节流反序列化为对象。这样可以实现不同计算机之间的对象数据交换,例如在分布式系统中,不同节点之间传递对象信息。
进程间通信:在同一台计算机上的不同进程之间进行通信时,也可以使用序列化来传递对象。通过将对象序列化后在进程间传递,接收方进程可以将其反序列化得到原始对象,从而实现进程间的信息共享和交互。
Java 序列化:Java 提供了内置的序列化机制,通过实现 java.io.Serializable 接口来标记可序列化的类。Java 序列化会将对象的状态信息转换为字节流,以便在本地存储或网络传输。
JSON:一种轻量级的数据交换格式,以文本形式表示数据,易于阅读和编写,也方便在不同语言和平台之间进行数据交换。许多编程语言都提供了对 JSON 序列化和反序列化的支持。
XML:一种标记语言,常用于数据的存储和传输。它具有良好的可读性和可扩展性,但相对 JSON 来说,数据格式较为冗长。
Protocol Buffers:由 Google 开发的一种高效的序列化格式,它将数据结构定义为 proto 文件,然后通过代码生成工具生成相应的序列化和反序列化代码。Protocol Buffers 具有高效、紧凑、跨语言等优点,常用于高性能的分布式系统中。
Apache Thrift:一种跨语言的序列化和 RPC 框架,它支持多种编程语言,并提供了丰富的数据类型和序列化机制。Thrift 可以根据定义的 IDL(接口定义语言)生成不同语言的代码,方便在不同语言的服务之间进行通信和数据交换。
反序列化是与序列化相反的过程,是将已序列化的数据恢复为原始对象的操作。
反序列化过程依据特定的规则和格式,将存储或传输中的数据(如字节流、JSON 字符串、XML 文档等)重新转换为内存中的对象。它会读取序列化后的数据,解析其中的信息,并按照对象的结构和类型重建对象,恢复对象的属性值和相关状态,使对象在内存中重新具备可操作性。
以下是一个实现了 Java 的 Serializable 接口的类的示例:
import java.io.Serializable; public class Person implements Serializable { public String name = null; public int age = 0; }
如你所见,Person 类实现了 Serializable 接口,但实际上并没有实现任何方法。如前所述,Serializable 接口只是一个标记接口,因此没有方法需要实现。
除了实现 Serializable 接口,用于序列化的类还应包含一个私有的、静态的、final 的 long 类型,名为 serialVersionUID 的变量。
下面是添加了 serialVersionUID 变量的 Person 类:
import java.io.Serializable; public class Person implements Serializable { // 看这里 private static final long serialVersionUID = 1234L; public String name = null; public int age = 0; }
在 Java 里,对象序列化 API 会借助 serialVersionUID 变量来判断反序列化对象的序列化(写入)版本,是否与当前尝试进行反序列化操作的类版本一致。
不妨想象这样一个场景:将一个 Person 对象序列化并存储到磁盘上,之后对 Person 类进行了修改,接着尝试对存储在磁盘中的 Person 对象进行反序列化操作。此时,被序列化的 Person 对象可能就与新版本的 Person 类不兼容了。
为了能够检测出这类问题,实现了 Serializable 接口的类应当包含 serialVersionUID 字段。要是对类进行了重大修改,也需要相应地更改其 serialVersionUID 的值。
幸运的是,Java SDK 以及众多 Java IDE 都提供了生成 serialVersionUID 的工具,所以你无需手动去完成这项工作。
在当下的技术环境中,众多 Java 项目倾向于采用有别于 Java 传统序列化机制的方式来处理 Java 对象的序列化。举例来说,Java 对象常被序列化为 JSON、BSON 或其他经过优化的二进制格式。这种做法的显著优势在于,非 Java 应用程序也能够读取这些对象。以网络浏览器中的 JavaScript 为例,它既可以将对象序列化为 JSON 格式,也能从 JSON 格式进行反序列化操作。
注意,这些新兴的对象序列化机制一般并不要求 Java 类实现 Serializable 接口。它们大多借助 Java 反射机制来对类进行检查,所以实现 Serializable 接口显得多余,并不会为序列化过程增添有用信息。
对象序列化本身就是一个话题,本 Java IO 教程主要侧重于 Stream 和 Reader。因此,目前我不会深入讨论对象序列化的细节。
如果要深入了解对象序列化技术,可以参考 Oracle 官网文章:
http://www.oracle.com/technetwork/articles/java/javaserial-1536170.html