Java-Serializable
序列化与反序列化
序列化:将Java对象转换为字节序列,以便持久化到磁盘或者网络传输。
反序列化:将磁盘文件或者网络文件中的字节序列恢复为原先的Java对象。
Java对象的序列化的方式有两种:
- 实现
Serializable
接口,比较方便。 - 实现
Exteranlizable
接口,需要重写writeExternal
和readExternal
方法。
定义User:
1 | public class User implements Serializable { |
序列化:
1 | public static void serialize(Object obj) throws IOException { |
反序列化:
1 | public static User deserialize() throws IOException, ClassNotFoundException { |
测试:
1 | public static void main(String[] args) throws Exception { |
控制台输出:
1 | User[id=1, username='Khighness', birth=Thu Apr 22 12:12:48 CST 2021] |
Serializable
看一下源代码:
1 | public interface Serializable { |
发现只是个空接口,因此这只是个标示性接口。
那我们点进刚才使用的writeObject
方法:
1 | public final void writeObject(Object obj) throws IOException { |
可以看到它调用的是writeObject0
方法,再进入这个方法:
1 | // remaining cases 判断对象 |
serialVersionUID
继续测试,给User类添加字段:
1 | private String email; |
将测试代码改为:
1 | public static void main(String[] args) throws Exception { |
运行结果:
1 | Exception in thread "main" java.io.InvalidClassException: top.parak.entity.User; local class incompatible: stream classdesc serialVersionUID = -5676367428859465228, local class serialVersionUID = -1182591043963610802 |
发现报错,并且抛出InvalidClassException
异常,提示信息:本地类不兼容,序列化前后的serialVersionUID
不同。
因此,有两个重要结论:
serialVersionUID
是序列化前后的唯一标识符。- 默认如果没有显示定义
serialVersionUID
,则编译器会为它自动声明一个。
扩展:
serialVersionUID
序列化ID,可以看成是序列化和反序列化过程中的暗号(连上彼此的讯号才有个依靠),在反序列化时,JVM会把字节流中的序列化ID和被序列号类中的序列化ID做对比,只有两者一致,才能重新反序列化,否则抛出异常终止反序列化的过程。- 如果在定义一个可序列化的类时,没有人为显示地给它定义一个
serialVersionUID
的话,则Java运行时环境会根据该类的各方面信息自动地为它生成一个默认的serialVersionUID
,一旦更改了类的结构或者信息,则类的serialVersionUID
也会跟着变化。
static&transient
继续测试,将User
类的email
字段添加修饰符static
或者transient
,发现测试成功。
于是可以得出结论,对于Serilizable
接口:
- 凡是被
static
修饰的字段是不会被序列化的 - 凡是被
transient
修饰的字段是不会被序列化的
transient
transient
关键字的作用就是把被修饰的字段的生命周期仅存于调用者的内存而不会持久化到磁盘中。
注意
实现Exteranlizable
接口时,transient
是无效的。
单例模式增强
单例模式下,对象经过序列化和反序列化之后还是一个对象吗?
可以做一个实验。
静态内部类的单例模式:
1 | public class Singleton implements Serializable { |
然后写一个测试方法:
1 | public static void main(String[] args) throws IOException, ClassNotFoundException { |
运行之后,发现控制台打印的是false
。
解决方法:在单例中重写readResolve
函数,直接返回单例对象,来规避之。
1 | public Object readResolve() { |
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自炒菜K殿下!
评论