简单来说,序列化是指将实现了Serializable接口的类的实例连同其状态通过ObjectOutputStream转变为字节数组保存下来,然后可以通过ObjectInputStream将其读取出来。Java提供的这种保存对象的方式可以用在对象持久化、对象传输等方面。
序列化
因为平时的工作中,用到Java对象序列化的地方不多,因此我只是比较简单的了解了一下序列化。把其中比较重要的几点内容总结一下:
-
Java序列化可以保存实例属性的状态,但是不会保存方法,因为方法没有状态。
-
Java序列化不会保存static变量,因为static变量是类的状态,序列化保存的是对象,而不是类。
-
transient关键字修饰的变量也不会被序列化保存,反序列化时,该变量会被设置为默认值。
-
父类实现了Serializable接口,其子类会继承,从而子类不需要显示实现Serializable接口。
-
子类实现了Serializable接口,其父类如未实现该接口,子类序列化不会保存父类属性的状态。
-
为了保证序列化和反序列化成功,对象必须有相同的序列化ID。
暂时能总结的也只有这么多,接下来就聊聊序列化与单例模式的关系。
序列化与单例模式
如果不是采用枚举的方式实现的单例模式,那么是可能被序列化破坏的。看看下面的例子:
public class Singleton implements Serializable{
private static volatile Singleton singleton = null;
private String s;
private Singleton(){}
public static Singleton getInstance(){
if (singleton == null){
synchronized (Singleton.class){
if (singleton == null)
singleton = new Singleton();
}
}
return singleton;
}
public String getS() {
return s;
}
public void setS(String s) {
this.s = s;
}
}
以上代码是一个单例模式的实现,Singleton实现了Serializable接口,并且保证了线程安全。有一个私有属性s,通过set和get方法进行赋值取值操作。下面来看序列化与反序列化:
public class SerializableTest{
public void serialzableSingleton(String file) throws IOException {
//序列化
ObjectOutputStream objectOutputStream = null;
FileOutputStream fileOutputStream = null;
Singleton singleton1 = Singleton.getInstance();
try {
fileOutputStream = new FileOutputStream(file);
objectOutputStream = new ObjectOutputStream(fileOutputStream);
singleton1.setS("This is a serializable test!");
objectOutputStream.writeObject(singleton1);//1. 序列化
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fileOutputStream != null){
fileOutputStream.close();
}
if (objectOutputStream != null){
objectOutputStream.close();
}
}
//反序列化
ObjectInputStream objectInputStream = null;
FileInputStream fileInputStream = null;
try {
fileInputStream = new FileInputStream(file);
objectInputStream = new ObjectInputStream(fileInputStream);
Singleton singleton2 = (Singleton) objectInputStream.readObject();//2. 反序列化
System.out.println(singleton2.getS());
System.out.println(singleton1 == singleton2);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
if (fileInputStream != null){
fileInputStream.close();
}
if (objectInputStream != null){
objectInputStream.close();
}
}
}
}
为了代码的规范,因此内容稍微有点多,但主要内容其实就是try里面的两段,最重要的就是注释1和2的两句。测试的结果为:This is a serializable test!和false。这说明序列化确实保存了属性的状态,并且反序列化后的对象singleton2和序列化之前的singleton1不是同一个对象,即单例模式被破坏了。那么对ObjectInputStream的readObject方法进行跟踪,发现了以下代码:
private Object readOrdinaryObject(boolean unshared)
throws IOException
{
if (bin.readByte() != TC_OBJECT) {
throw new InternalError();
}
ObjectStreamClass desc = readClassDesc(false);
desc.checkDeserialize();
Class<?> cl = desc.forClass();
if (cl == String.class || cl == Class.class
|| cl == ObjectStreamClass.class) {
throw new InvalidClassException("invalid class descriptor");
}
Object obj;
try {
obj = desc.isInstantiable() ? desc.newInstance() : null;//1.对象是否可以进行实例化
} catch (Exception ex) {
throw (IOException) new InvalidClassException(
desc.forClass().getName(),
"unable to create instance").initCause(ex);
}
passHandle = handles.assign(unshared ? unsharedMarker : obj);
ClassNotFoundException resolveEx = desc.getResolveException();
if (resolveEx != null) {
handles.markException(passHandle, resolveEx);
}
if (desc.isExternalizable()) {
readExternalData((Externalizable) obj, desc);
} else {
readSerialData(obj, desc);
}
handles.finish(passHandle);
if (obj != null &&
handles.lookupException(passHandle) == null &&
desc.hasReadResolveMethod())
{
Object rep = desc.invokeReadResolve(obj);//2. 执行readResolve方法
if (unshared && rep.getClass().isArray()) {
rep = cloneArray(rep);
}
if (rep != obj) {
handles.setObject(passHandle, obj = rep);//3. 将需要返回的obj指向rep
}
}
return obj;
}
虽然代码有点长,但我标注出了其中最重要的两句。注释1表示如果对象可以在运行时被实例化,则使用反射新建一个实例,否则返回null。因此,序列化也是使用反射新建了一个实例,破坏了单例模式。那么注释2就是避免序列化破坏单例的一种方式。我们只需要在单例的类中实现readResolve方法,利用它返回其实例,那么序列化得到的对象就是那个唯一的实例。
private Object readResolve(){
return singleton;
}
如果singleton存在readResolve方法,序列化的时候就会执行该方法,在该方法中返回该唯一的实例,就避免了反序列化返回一个新的Singleton实例。
总结
这里的重点是序列化通过反射生成新的实例破坏了单例模式,以及如何通过readResolve方法避免单例模式被破坏。只是稍微读了一下其源码,读懂的感觉真是爽,读源码还真是非常好的学习方法。