文章来源于Hollis干货集散地
//新建实体类实现序列化接口
public class People implements Serializable {
private static final long serialVersionUID = 4547806378767721442L;
private String name;
private Integer age;
}
public static void main(String[] args) throws Exception {
//通过下面代码对People类进行序列化与反序列化
People people = new People();
people.setName("张三");
people.setAge(23);
System.out.println(people);
//写入文件
ObjectOutputStream oos = null;
try {
oos = new ObjectOutputStream(new FileOutputStream("tempFile"));
oos.writeObject(people);
} catch (IOException e) {
e.printStackTrace();
} finally {
oos.close();
}
//读出文件
File file = new File("tempFile");
ObjectInputStream ois = null;
try {
ois = new ObjectInputStream(new FileInputStream(file));
People newUser = (People) ois.readObject();
System.out.println(newUser);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
File file1 = new File("tempFile");
file1.deleteOnExit();
ois.close();
}
}
}
//打印
People(name=张三, age=23)
People(name=张三, age=23)
对于这段代码大家都很熟悉,新建实体类并实现序列化接口,那么什么是序列化呢?
1.序列化与反序列化
序列化:是将对象转换为可传输格式的过程,一般以字节码或XML格式传输,是一种数据的持久化手段,一般广泛应用于网络传输,RMI和RPC场景中。
反序列化:字节码或XML编码格式可以还原为完全相等的对象,这个相反的过程称为反序列化。
2.如何实现序列化与反序列化
Java提供了一套java对象序列化及反序列化的API,其中包括如下接口和类:java.io.Serializable、java.io.Externalizable、ObjectOutput、ObjectInput、ObjectOutputStream、ObjectInputStream.
Serializable 接口
类通过实现Serializable 接口实现其序列化功能,可序列化类的所有子类型本身都是可序列化的。序列化接口没有方法或字段,仅用于标识可序列化的语义。若没实现该接口,则报java.io.NotSerializableException。
Externalizable接口
Externalizable继承了Serializable,该接口中定义了两个抽象方法:writeExternal()与readExternal()。当使用Externalizable接口来进行序列化与反序列化的时候需要开发人员重写writeExternal()与readExternal()方法。在使用Externalizable进行序列化的时候,在读取对象时,会调用被序列化类的无参构造器去创建一个新的对象,然后再将被保存对象的字段的值分别填充到新对象中。所以,实现Externalizable接口的类必须要提供一个public的无参的构造器。
ObjectOutput和ObjectInput 接口
ObjectInput 扩展自 DataInput 接口以包含对象的读操作。DataInput 接口用于从二进制流中读取字节,并根据所有 Java 基本类型数据进行重构。同时还提供根据 UTF-8 修改版格式的数据重构 String 的工具。对于此接口中的所有数据读取例程来说,如果在读取所需字节数之前已经到达文件末尾 (end of file),则将抛出 EOFException(IOException 的一种)。如果因为到达文件末尾以外的其他原因无法读取字节,则将抛出 IOException 而不是 EOFException。尤其是,在输入流已关闭的情况下,将抛出 IOException。
ObjectOutput 扩展 DataOutput 接口以包含对象的写入操作。DataOutput 接口用于将数据从任意 Java 基本类型转换为一系列字节,并将这些字节写入二进制流。同时还提供了一个将 String 转换成 UTF-8 修改版格式并写入所得到的系列字节的工具。对于此接口中写入字节的所有方法,如果由于某种原因无法写入某个字节,则抛出 IOException。
ObjectOutputStream类和ObjectInputStream类
通过前面的代码片段中我们也能知道,我们一般使用ObjectOutputStream的writeObject方法把一个对象进行持久化。再使用ObjectInputStream的readObject从持久化存储中把对象读取出来。
3.如何让某些成员变量不被序列化?
可以在成员变量前用transient关键字修饰,被修改的变量则不被序列化,然后在被反序列化后,transient变量的值被设为初始值,如int为0,对象型为null。
4.serialVersionUID 有什么作用?
虚拟机是否允许反序列化取决于serialVersionUID 是否一致,否则就会出现序列化版本不一致的异常,即是InvalidCastException,这样做是为了保证安全,因为文件存储中的内容可能被篡改。如果没有显示的定义serialVersionUID ,则会根据编译的class自动生成一个serialVersionUID 做比较。
5.序列化是如何破坏单例模式的?
单例类
//使用双重校验锁方式实现单例
public class Singleton implements Serializable {
private volatile static Singleton singleton;
private Singleton(){
}
public Singleton getSingleton(){
if (singleton == null) {
synchronized (Singleton.class){
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
测试类
public static void main(String[] args) throws Exception {
//序列化与反序列化
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("tempFile"));
oos.writeObject(Singleton.getSingleton());
oos.close();
//Read Obj from file
File file = new File("tempFile");
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
Singleton newInstance = (Singleton) ois.readObject();
//判断是否是同一个对象
System.out.println(newInstance == Singleton.getSingleton());
ois.close();
}
//打印
false 说明通过序列化与反序列化生成了一个新对象,破坏了Singleton的单利性
为什么会产生这样的后果?
ObjectInputStream
对象的序列化过程通过ObjectOutputStream和ObjectInputputStream来实现的,ObjectInputStream的readObject的调用栈为:readObject—>readObject0—>readOrdinaryObject—>checkResolve
readOrdinaryObject方法片段(省略部分代码)
Object obj; //为此方法的返回对象
try {
//isInstantiable:如果一个serializable/externalizable的类可以在运行时被实例化,那么该方法就返回true
//desc.newInstance:该方法通过反射的方式调用无参构造方法新建一个对象
obj = desc.isInstantiable() ? desc.newInstance() : null;
} catch (Exception ex) {
throw (IOException) new InvalidClassException(
desc.forClass().getName(),
"unable to create instance").initCause(ex);
}
此时可看到序列化破坏单例模式的原因为:序列化时通过无参构造新建一个对象。
如何防止序列化破坏单例模式
在Singleton 类中定义readResolve就可以解决该问题:
public class Singleton implements Serializable{
private volatile static Singleton singleton;
private Singleton (){}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
private Object readResolve() {
return getSingleton();
}
}
原因
在readOrdinaryObject方法中有另一个代码片段:
if (obj != null &&
handles.lookupException(passHandle) == null &&
//hasReadResolveMethod:如果实现了serializable 或者 externalizable接口的类中包含readResolve则返回true
desc.hasReadResolveMethod())
{
//invokeReadResolve:通过反射的方式调用要被反序列化的类的readResolve方法。
Object rep = desc.invokeReadResolve(obj);
if (unshared && rep.getClass().isArray()) {
rep = cloneArray(rep);
}
if (rep != obj) {
// Filter the replacement object
if (rep != null) {
if (rep.getClass().isArray()) {
filterCheck(rep.getClass(), Array.getLength(rep));
} else {
filterCheck(rep.getClass(), -1);
}
}
handles.setObject(passHandle, obj = rep);
}
}
这样就可以防止序列化对单利的破坏了。