在设计模式之单例模式中,简单介绍了什么是单例模式,怎样实现自己的单例模式,在文中也提到一个关键点,不知道大家注意没有:序列化和反序列化会影响破坏单例模式。这里先给结论待会再给大家解释为什么?因为反序列化的时候会去反射调用对象的无参构造方法。
今天带着几个问题来学习:
1、怎么实现序列化?
2、transient关键字的作用?
3、如何自定义序列化?
4、自定义的序列化是如何被调用的?为什么就生效了?
5、ArrayList的序列化思想有什么好处?
一、如何实现序列化?
在Java中要如果我们要涉及到文件操作或者网络传输都离不开序列化,他会保证我们序列化和反序列后得到的对象一致。要实现序列化也简单,Java从1.1版本开始就提供了一个接口 java.io.Serializable,只要我们实现这个接口就能实现序列化。
二、transient关键字的作用
在实现了Serializable接口的类中,所有被transient关键字修饰的属性都不会被序列化和反序列化(注意:序列化只会关注在对象的状态上面,即动态属性,静态属性由于是在JVM装载类的时候就会初始化,所有不会实例化。)
写一个被序列化的User类,注释都写的比较清楚了。
在main方法中写一个测试User方法:
readObejct方法是把一个对象写到一个文件中在读取出来返回:
下面运行这个方法:
可以看到name 和 sex 被序列化了 但是age 为什么为“null”呢?应该age 没有被序列化进去,反序列化的时候会有默认值,除了基本类型如int=0外其他的对象类型都是null;
那么问题来了,对ArrayList源码了解的朋友应该看到:
这个elemntData就是数组元素,序列化的时候也是把所有的elementData序列化到文件;明明有transient为什么可以被序列化呢?
readObejct还是刚刚那个相同的方法。
可以看到都被序列化出来了这是为什么呢?
这里先给结论:
在序列化过程中,如果被序列化的类中定义了writeObject和readObject方法,虚拟机会试图调用对象类里的writeObject和readObject方法,进行用户自定义的序列化和反序列化。
如果没有这样的方法,则默认调用是ObjectOutputStream的defaultWriteObject方法以及ObjectInputStream的defaultReadObject方法。
用户自定义的writeObject和readObject方法可以允许用户控制序列化的过程,比如可以在序列化的过程中动态改变序列化的数值。
我们能够注意到在ArrayList源码中有这样的方法:
好吧,即使你有这两个方法,但是为什么我序列化的时候就会调用这两个方法了在哪里调用的呢?
这里大家就要去看ObjectOutputStream和ObjectInputsStream的源码了,调用链是:writeObejct —>writeObject0—>writeOrdinary—>ObjectwriteSerialData——>invokeWriteObject;
最好亲自去看看这个调用链,里面大有玄机。
好啦之前的三个问题也都解决了:
如何自定义序列化?只要实现自己的readObject或者writeObject 就行了。
自定义的如何被调用的?就是在ObjectOutputStream的输出的时候调用的他会去看有没有用户自定义的writeObject,如果没有就会调用默认的。有就调用用户自定义的。
那么为什么ArrayList 要用这种方式来序列化呢?
不知道大家注意看上面的代码没有?有这样一段:List<String> list =new ArrayList<String>(100);
这里为什么我初始化了100个size的大小,序列化反序列化后输出的就五个呢?对的,实际只放了五个元素,那就会序列化95个null元素。为了保证在序列化的时候不会将这么多null同时进行序列化。
文章开始的问题:为什么序列化会影响单例模式:
在invokeWriteObject 中使用了反射去创建新的对象;解决的方式就是在序列化的类中添加readRsolve
方法;其实最好的实现单例的方式就是枚举还不用添加这个方法。