java.ioSerializable { }
是一个空接口,能够见是一个标志性接口。注解中说明没有实现此接口的类将不会对其状态进行任何序列化或反序列化,JVM经过这个标识来识别是否须要序列化。java
Serializable是java.io包中定义的、用于实现Java类的序列化操做而提供的一个语义级别的接口。Serializable序列化接口没有任何方法或者字段,只是用于标识可序列化的语义。实现了Serializable接口的类能够被ObjectOutputStream转换为字节流,同时也能够经过ObjectInputStream再将其解析为对象。例如,咱们能够将序列化对象写入文件后,再次从文件中读取它并反序列化成对象,也就是说,可使用表示对象及其数据的类型信息和字节在内存中从新建立对象。数据库
而这一点对于面向对象的编程语言来讲是很是重要的,由于不管什么编程语言,其底层涉及IO操做的部分仍是由操做系统其帮其完成的,而底层IO操做都是以字节流的方式进行的,因此写操做都涉及将编程语言数据类型转换为字节流,而读操做则又涉及将字节流转化为编程语言类型的特定数据类型。而Java做为一门面向对象的编程语言,对象做为其主要数据的类型载体,为了完成对象数据的读写操做,也就须要一种方式来让JVM知道在进行IO操做时如何将对象数据转换为字节流,以及如何将字节流数据转换为特定的对象,而Serializable接口就承担了这样一个角色。编程
咱们能够经过例子来实现将序列化的对象存储到文件,而后再将其从文件中反序列化为对象,代码示例以下:数组
先定义一个序列化对象User:网络
User Serializable { Integer String (Integer idString name) { .= id.= name}
先测试将该对象写入一个文件(这使用SpringBoot创建了一个能够访问都接口,在访问这个接口的时候执行了文件的写入操做):mybatis
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中:app
看到对象数据被持久化到了磁盘文件中。框架
去掉实体类中的Serializable接口编程语言
User{ Integer String (Integer idString name) { .= id.= name}
再次运行接口,报错以下:ide
抛出了NotSerializableException异常,提示非可序列化异常,也就是说没有实现Serializable接口的对象是没法经过IO操做持久化的,由于JVM没有自动将对象转化为字节流,因此在程序没有作任何操做的时候user对象是没法直接被存储到持久层。
将以前持久化写入test.txt文件的对象数据再次转化为Java对象,代码以下:
序列化:将 Java 对象转换成字节流的过程。就是把内存中的这些对象变成一连串的字节(bytes)描述的过程。
反序列化:将字节流转换成 Java 对象的过程。
一、须要把内存中的对象状态数据保存到一个文件或者数据库中的时候,例如咱们利用mybatis框架编写持久层insert对象数据到数据库中时
二、网络通讯时须要用套接字在网络中传送对象时,如咱们使用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值的要求。