一、Serializable 接口概述
-
Serializable
是java.io
包中定义的、用于实现Java
类的序列化和反序列化操作而提供的一个语义级别的接口,告诉JVM
此类可被序列化,可被默认的序列化机制序列化 -
序列化是指将对象转换为字节序列的过程,我们称之为对象的序列化,就是将内存中的这些对象变成一连串的字节描述过程
-
反序列化就是将持久化的字节文件数据恢复为指定对象的过程
-
Serializable
序列化接口没有任何方法或者字段,是一个空类,只是用于标志可序列化的语义,只要实现了Serializable
接口的类都可以被序列化和反序列化// 源码如下 package java.io; public interface Serializable { }
-
实现了
Serializable
接口的类可以被ObjectOutputStream
转换为字节流,同时也可以通过ObjectInputStream
再将字节流解析为指定得对象
二、Serializable 使用场景
无论什么编程语言,其底层涉及到 IO
操作的部分都是交由操作系统帮其完成的,而底层 IO
操作都是以字节流的方式进行的,所以写操作都涉及将编程语言数据类型转换为字节流,而读操作则又涉及将字节流转换为相应编程语言的特定数据类型。Java
作为一门面向对象的编程语言,对象作为其主要数据的类型载体,为了完成对象数据类型的读写操作,也就需要一种方式来让 JVM
知道在进行 IO
操作的时候如何将对象数据转换为字节流,以及如何将字节流转换为特定对象时,匹配到正确的数据类型,而 Serializable
接口就承担了这样一个角色,告诉负责完成这些操作的 JVM
保证 Java
对象序列化和反序列化的时候,对象数据结构的准确性。
-
将内存中的对象写入到磁盘的场景
比如内存不够用的时候,计算机就要将内存中的部分对象暂时保存到硬盘中,等到需要再次使用的时候,再将写入到硬盘文件中的字节流反序列化出来
-
采用
Socket
将对象进行网络传输的场景在进行
Java
的Socket
编程时,有时候需要将某一类对象传输到访问服务器,那么被传输的类就需要实现Serializable
接口,可以将数据转换为二进制来传输 -
通过
RMI
传输对象的场景如果要通过远程方法调用(
RMI
)去调用一个远程对象的方法,如在计算机A
中调用另一台计算机B
对象的方法,那么你需要通过JNDI
服务获取计算机B
目标对象的引用,将对象从B
传到A
,就需要实现序列化接口。 -
Serializable
接口就是Java
提供用于进行高效率的异地共享实例对象的机制,实现这个接口并定义serialVersionUID
即可
三、Serializable 示例演示
-
创建演示对象
User
并实现Serializable
接口public class User implements Serializable { private static final long serialVersionUID = 1L; private int id; private int userId; private String userName; }
-
编写测试类将
User
对象序列化到磁盘文件private static final String FILEPATH = "/Users/rambo/Desktop/Temp/user.txt"; @Test public void serialObjectToFile() throws IOException { User user = new User(1, 123, "Rambo"); // 1. 创建文件输出流 FileOutputStream fileOutputStream = new FileOutputStream(FILEPATH); // 2. 创建对象输出流 ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream); // 3. 将 User 对象写入到输出流中并保存到文件 objectOutputStream.writeObject(user); // 4. 关闭输对象输出流 objectOutputStream.close(); // 5. 控制台提示信息 System.out.println("成功序列化对象到磁盘文件"); }
P.S
通过以上方式得到的序列化后的二进制流保存在
txt
文本中的内容如下¬ísr%com.rambo.springbootsample.model.UserIidIuserIdLuserNametLjava/lang/String;xp{tRambo
-
将
User
对象中的Serializable
实现代码片段去掉,再次执行序列化的测试类java.io.NotSerializableException: com.rambo.springbootsample.model.User at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184) at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348) at com.montnets.model.UserTest.serialObjectToFile(UserTest.java:29) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56) at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306) at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100) at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63) at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329) at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293) at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306) at org.junit.runners.ParentRunner.run(ParentRunner.java:413) at org.junit.runner.JUnitCore.run(JUnitCore.java:137) at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68) at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33) at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:230) at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:58)
P.S
也就是说没有实现
Serializable
接口的对象是无法通过IO
操作序列化的 -
将
User
类再次实现序列化,并执行序列化到磁盘后,编写反序列化测试类并执行@Test public void unSerialFileToObject() throws IOException, ClassNotFoundException { // 1. 创建文件输入流 FileInputStream fileInputStream = new FileInputStream(FILEPATH); // 2. 创建对象输入流 ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream); // 3. 将输入流中的数据转换为 User 对象 User user = (User) objectInputStream.readObject(); // 4. 打印 User 对象校验反序列化结果 System.out.println("反序列化磁盘文件转为对象成功:\n" + user.toString()); }
P.S
通过反序列化操作,可以再次将序列化到对象字节流通过
IO
转换为Java
对象,结果如下反序列化磁盘文件转为对象成功: User(id=1, userId=123, userName=Rambo)
-
再次将
User
对象中的Serializable
实现代码片段去掉,再次执行反序列化的测试类java.io.InvalidClassException: com.rambo.springbootsample.model.User; class invalid for deserialization at java.io.ObjectStreamClass$ExceptionInfo.newInvalidClassException(ObjectStreamClass.java:169) at java.io.ObjectStreamClass.checkDeserialize(ObjectStreamClass.java:874) at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2097) at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1624) at java.io.ObjectInputStream.readObject(ObjectInputStream.java:464) at java.io.ObjectInputStream.readObject(ObjectInputStream.java:422) at com.montnets.model.UserTest.unSerialFileToObject(UserTest.java:43) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56) at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306) at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100) at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63) at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329) at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293) at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306) at org.junit.runners.ParentRunner.run(ParentRunner.java:413) at org.junit.runner.JUnitCore.run(JUnitCore.java:137) at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68) at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33) at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:230) at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:58)
P.S
提示无效类型异常,说明在Java
中如要实现对象到IO
读写操作,都必须要实现Serializable
接口
四、关于 SerialVersionUID
-
实验演示
-
紧接着上述到示例,我们将
User
对象中的serialVersionUID = 1L
修改为serialVersionUID = 2L
,再次执行反序列化的测试类,效果如下java.io.InvalidClassException: com.rambo.springbootsample.model.User; local class incompatible: stream classdesc serialVersionUID = 1, local class serialVersionUID = 2 at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:699) at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1939) at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1805) at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2096) at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1624) at java.io.ObjectInputStream.readObject(ObjectInputStream.java:464) at java.io.ObjectInputStream.readObject(ObjectInputStream.java:422) at com.montnets.model.UserTest.unSerialFileToObject(UserTest.java:43) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56) at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306) at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100) at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63) at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329) at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293) at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306) at org.junit.runners.ParentRunner.run(ParentRunner.java:413) at org.junit.runner.JUnitCore.run(JUnitCore.java:137) at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68) at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33) at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:230) at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:58)
-
通过实验结果可以看到,
serialVersionUID
的值其实是一个标记,在进行序列化和反序列化的时候,让JVM
能识别采用哪个类来接收此实例
-
-
结果分析
对于
JVM
来说,要进行持久化的类必须要有一个标记,只有持有这个标记,JVM
才允许类创建的对象可以通过其IO
系统转换为字节数据,从而实现序列化后的持久化,而这个标记就是serialVersionUID
属性。而在反序列化的过程中则需要使用serialVersionUID
来确定由哪个类来加载这个对象,所以我们在实现Serializable
接口的时候,一般还要尽量显示地定义serialVersionUID
private static final long serialVersionUID = 1L;
-
在反序列化的过程中,如果接收方为对象加载了一个类,如果该对象的
serialVersionUID
与对应持久化时类的serialVersionUID
不同,那么反序列化到过程中将会导致InvalidClassException
异常 -
如果我们在序列化对象之前,没有对该对象显示的声明
serialVersionUID
,则序列化进行时JVM
将会根据该类的各个方面计算该类默认的serialVersionUID
值。但是Java
官方强烈建议所有要序列化的类都显示到声明serialVersionUID
字段,因为如果高度依赖于JVM
默认生成serialVersionUID
,可能会导致其与编译器的实现细节耦合,这样可能导致在反序列化的过程中发生意外的InvalidClassException
异常 -
声明
serialVersionUID
字段尽可能采用private
关键字来修饰,因为该字段的声明只适用于声明的类,该字段作为成员变量被子类继承并没有什么用处。有个特别到地方需要注意:数组类是不需要显示地声明serivalVersionUID
字段的。因为它们始终具有默认计算值,不过数组类反序列化也是放弃了匹配serialVersionUID
值的要求
-