java.ioSerializable {}
是一个空接口,可以见是一个标志性接口。注解中说明没有实现此接口的类将不会对其状态进行任何序列化或反序列化,JVM通过这个标识来识别是否需要序列化。
Serializable接口概述Serializable是java.io包中定义的、用于实现Java类的序列化操作而提供的一个语义级别的接口。Serializable序列化接口没有任何方法或者字段,只是用于标识可序列化的语义。实现了Serializable接口的类可以被ObjectOutputStream转换为字节流,同时也可以通过ObjectInputStream再将其解析为对象。例如,我们可以将序列化对象写入文件后,再次从文件中读取它并反序列化成对象,也就是说,可以使用表示对象及其数据的类型信息和字节在内存中重新创建对象。
而这一点对于面向对象的编程语言来说是非常重要的,因为无论什么编程语言,其底层涉及IO操作的部分还是由操作系统其帮其完成的,而底层IO操作都是以字节流的方式进行的,所以写操作都涉及将编程语言数据类型转换为字节流,而读操作则又涉及将字节流转化为编程语言类型的特定数据类型。而Java作为一门面向对象的编程语言,对象作为其主要数据的类型载体,为了完成对象数据的读写操作,也就需要一种方式来让JVM知道在进行IO操作时如何将对象数据转换为字节流,以及如何将字节流数据转换为特定的对象,而Serializable接口就承担了这样一个角色。
一个实验,Show me the code【实验一】我们可以通过例子来实现将序列化的对象存储到文件,然后再将其从文件中反序列化为对象,代码示例如下:
先定义一个序列化对象User:
User Serializable { Integer String (Integer idString name) { .= id.= name}
先测试将该对象写入一个文件(这使用SpringBoot建立了一个可以访问都接口,在访问这个接口的时候执行了文件的写入操作):
HelloWorldController { (= = RequestMethod.) User (String name) { User user = User(name){ ObjectOutputStream objectOutputStream = ObjectOutputStream(FileOutputStream())objectOutputStream.writeObject(user)objectOutputStream.close()} (IOException e) { e.printStackTrace()} user}}
访问这个接口(我本机地址):http://127.0.0.1:8080/hello/say 后, 我们就将User对象及其携带的数据写入了文本test.txt中:
看到对象数据被持久化到了磁盘文件中。
【实验二】去掉实体类中的Serializable接口
User{ Integer String (Integer idString name) { .= id.= name}
再次运行接口,报错如下:
抛出了NotSerializableException异常,提示非可序列化异常,也就是说没有实现Serializable接口的对象是无法通过IO操作持久化的,因为JVM没有自动将对象转化为字节流,所以在程序没有做任何操作的时候user对象是无法直接被存储到持久层。
【实验三】将之前持久化写入test.txt文件的对象数据再次转化为Java对象,代码如下:
序列化:将 Java 对象转换成字节流的过程。就是把内存中的这些对象变成一连串的字节(bytes)描述的过程。
反序列化:将字节流转换成 Java 对象的过程。
使用场景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值的要求。