Java--序列化知识点
今天线上遇到了DTO类实现了Serializable
接口,但是其并没有显示声明serialVersionUID
,这样的话每次打包有改动JDK就会为其重新生成serialVersionUID
.这就带来了不同版本之间的实体类可能反序列化不成功,线上RPC调用出现了问题.那么就深入探讨一下原因.
Serializable的作用
看该类的JDK注释可以发现The serialization interface has no methods or fields and serves only to identify the semantics of being serializable.
也就是说Serializable是一个标识接口,和Cloneable
接口等一样的效果.
如下面的User类,实现了序列化接口,并使用serialVersionUID
标识其序列化对应的ID序号.
1 | static class User implements Serializable { |
如何序列化
java.io.ObjectOutputStream
代表对象输出流,其使用writeObject()方法把对象实例转换为字节流然后写入到文件,或者用于网络传输.
1 |
|
如何反序列化
java.io.ObjectInputStream
代表对象输入流,其使用readObject()方法读取序列化的字节,然后再转换为对象.
1 |
|
serialVersionUID的作用
按照上面代码,序列化和反序列化都是成功的,如果在已经序列化后,对User要作修改,增加一个email字段,再试试反序列化.
1 | static class User implements Serializable { |
程序会正常运行,而且这个email会被很智能的初始化为null.
修改serialVersionUID
为1L再试试.
1 | java.io.InvalidClassException: cn.edu.aust.test.ObjectTest$User; local class incompatible: stream classdesc serialVersionUID = 5768430629641297769, local class serialVersionUID = 1 |
报错很明显,两边类的serialVersionUID
不一样,也就是说对于编译好的class,其serialVersionUID
是其序列化的唯一标识,如果未显示声明JDK则会自动为其加上,换句话说serialVersionUID
保证了对象的向上兼容,可以使用命令seriserialver
可以查看一个class文件的serialVersionUID
,当线上版本忘记加该字段的时候该命令还是很有用处的.
1 | seriserialver cn.edu.aust.test.ObjectTest\$User |
另外需要注意反序列化因为是直接从字节流里面构造出对象,因此并不会去执行构造函数.如果你的类有在构造函数中初始值的行为,那么这里就可能得到异常.
transient的作用
transient翻译为瞬时,也就是被其修饰的变量序列化时会忽略该字段.什么时候需要用到这个字段呢?
在Java中对象之间的关系会组成一个对象图,序列化的过程是对该对象图的遍历,那么反序列化也仍然是对该对象图的遍历.对于对象里面的对象就是递归过程,对于链表之类的数据结构递归的话很容易引起栈溢出,那么就可以使用transient
忽略该字段.
使用自己定制的序列化规则那么需要声明serialVersionUID吗?
最好声明下,因为你不能保证你用的第三方库使用的不是jdk序列化方式. 比如Spring data redis使用的默认序列化规则就是jdk默认序列化.
- 版权声明: 感谢您的阅读,本文由屈定's Blog版权所有。如若转载,请注明出处。
- 文章标题: Java--序列化知识点
- 文章链接: https://mrdear.cn/posts/java_serializable.html