前言
在上一篇Android学习博客中聊到了Android多进程模式,当我们需要通过Intent和Binder传输数据时就需要使用Serializable
或者Parcelable
。还有如果我们需要把对象持续化到储存设备上或者通过网络传输给其他客户端,这个时候也需要使用Serializable
来完成对象的持续化。这篇博客介绍如果使用Serializable
完成对象的的序列化。
Serializable接口
Serializable
是Java所提供的一个序列化接口,它是一个空接口,为对象提供标准的序列化和反序列化操作。
利用Serializable序列化和反序列化
1.建立一个User类,实现Serializable接口
package com.wyb.demo;
import java.io.Serializable;
public class User implements Serializable{
private static final long serialVersionUID = 1L;
private int userId;
private String userName;
private boolean isMale;
public User(int userId, String userName, boolean isMale) {
this.userId = userId;
this.userName = userName;
this.isMale = isMale;
}
public int getUserId() {
return userId;
}
public void setUserId(int userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public boolean isMale() {
return isMale;
}
public void setMale(boolean isMale) {
this.isMale = isMale;
}
}
2.创建UserTest类实现序列化和反序列化
package com.wyb.demo;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class UserTest {
public static void main(String[] args) {
// 实现序列化
writeSerializableObject();
// 实现反序列化
readSerializableObject();
}
public static void writeSerializableObject() {
User user = new User(0, "test", true);
try {
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("cache.text"));
out.writeObject(user);
out.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void readSerializableObject() {
try {
ObjectInputStream in = new ObjectInputStream(new FileInputStream("cache.text"));
try {
User newUser = (User) in.readObject();
System.out.println("元素1:"+newUser.getUserId()+"元素2:"+newUser.getUserName()+
"元素3:"+newUser.isMale());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
in.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
上述代码是利用Serializable方式序列化对象的过程,只需要把实现了Serializable接口的User对象写到文件中就可以快速恢复了,恢复后的对象newUser和user的内容完全一样的,但是两者不是同一个对象。
serialVersionUID
在日常的开发中,我们不指定serialVersionUID
也是可以实现序列化。我们会产生需不需要添加的疑问,它的作用是用来辅助序列化和反序列化过程的。原则上序列化后的数据中的serialVersionUID
只有当前的类的serialVersionUID
相同才能正常的进行反序列化。
serialVersionUID的工作机制
序列化的时候系统会把当前类的serialVersionUID
写入序列化文件中(也可能是其他中介),当反序列化的时候系统会把当前类的serialVersionUID
,当它是否和当前类的serialVersionUID
一致,如果一致就说明序列化的类的版本和当前类的版本是相同的,这个时候可以成功反序列化;否则说明当前类和序列化的类相比发生了某些变换,比如成员变量的数量、类型可能发生了改变,这个时候是无法正常反序列化的,因此会报如下错误:
java.io.InvalidClassException:Main;
一般来说,我们应该手动指定serialVersionUID
的值,比如1L
,也可以让Eclipse根据当前类的结构自动去生成它的hash值,这样序列化和反序列化时两者的serialVersionUID
是相同的,因此可以正常进行反序列化。如果不手动指定serialVersionUID
的值,反序列化时当前类有所改变,比如增加或者删除了某些成员变量,那么系统就会重新计算当前类的hash值并把它赋值给serialVersionUID
,这个时候当前类的serialVersionUID
就和序列化的数据中的serialVersionUID
不一致,于是反序列化失败,程序就会出现crash
.
所以我们可以明显感觉到serialVersionUID
的作用,当我们手动指定了它以后,就可以在很大程度上避免反序列化过程的失败。比如当版本升级后,我们可能删除了某个成员变量也可能增加了一些新的成员变量,这个时候我们的反向序列化过程仍然能够成功,程序仍然能够最大限度地恢复数据,相反如果不指定serialVersionUID
的话,程序则会挂掉。当然我们还要考虑另外一种情况,如果类结构发生了非常规性改变,比如修改了类名,修改了成员变量的类型,这个时候尽管serial VersionUID
验证通过了,但是反序列化过程还是会失败,因为类结构有了毁灭性的改变,根本无法从老版本的数据中还原出一个新的类结构的对象。
根据上面的分析,我们可以知道,给serialVersionUID
指定为1L
或者采用Eeclipse
根据当前类结构去生成的hash
值,这两者并没有本质区别,效果完全一样。以下两点需要特别提一下,首先静态成员变量属于类不属于对象,所以不会参与序列化过程;其次用transient关键字标记的成员变量不参与序列化过程。