简介
Java 提供了一种对象序列化的机制,该机制中,一个对象可以被表示为一个字节序列,该字节序列包括该对象的数据、有关对象的类型的信息和存储在对象中数据的类型。
将序列化对象写入文件之后,可以从文件中读取出来,并且对它进行反序列化,也就是说,对象的类型信息、对象的数据,还有对象中的数据类型可以用来在内存中新建对象。
序列化
在Java中,欲序列化的类需要实现Serializable/Externalizable
接口。
序列化使用ObjectOutputStream.writeObject()方法
public static void Serialize(Object o){
try{
FileOutputStream fileOut =
new FileOutputStream("out/test.ser");
ObjectOutputStream out = new ObjectOutputStream(fileOut);
out.writeObject(o);
out.close();
fileOut.close();
}catch(IOException i){
i.printStackTrace();
}
}
Serializable
对于实现Serializable
接口的类来说,只有当它的所有属性都是可序列化的,或不可序列化的属性都是transient
的时,它才可被序列化。如下面的Test
类在序列化的时候就会报错:
public class Test implements Serializable{
private final String name;
private final Unserializable uns;
public Test(String name, Unserializable uns){
this.name = name;
this.uns = uns;
}
}
public class Unserializable {
private final String name;
public Unserializable(String name){
this.name = name;
}
}
但若把Test类中uns属性的private final
变为private final transient
,Test类就可以正常序列化。
transient属性
Serializable接口实现序列化的方式是将类中的所有属性均序列化,而当我们不想将其中的某个属性序列化时,可以在该属性用transient修饰。
public class Test implements Serializable{
private final String name;
private final transient Unserializable uns;
public Test(String name, Unserializable uns){
this.name = name;
this.uns = uns;
}
}
Externalizable
该接口继承自Serializable
接口,需要重写其中的writeExternal()
与readExternal()
方法。通过这两个方法可以自定义欲序列化/反序列化的属性。
以下面的Test类
为例,在writeExternal
方法中可以自由指定要序列化的属性,以及它们的顺序(在readExternal
方法中的顺序需相同)。
以这种方法实现序列化的类无法将欲序列化的属性设置为final
。
public class Test implements Externalizable{
private String name;
private int ID;
private float age;
private final Unserializable uns;
public Test(){}
public Test(String name, int ID, float age, Unserializable uns){
this.name = name;
this.ID = ID;
this.age = age;
this.uns = uns;
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(name);
out.writeObject(ID);
out.writeObject(age);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
this.name = (String) in.readObject();
this.ID = (int) in.readObject();
this.age = (float) in.readObject();
}
}
实现Externalizable
接口的类必须提供一个public的无参构造器,否则会抛出一个InvalidClassException
异常。
注
- 静态变量不会被序列化
- 当一个父类实现序列化,子类自动实现序列化,不需要显式实现Serializable接口
反序列化
Java中,类的反序列化可以使用ObjectInputStream.readObject()
方法实现。
下面的方法实现了反序列化,输入文件路径,返回一个对象。
public static Object Deserialize(String filepath){
try{
FileInputStream fileIn = new FileInputStream(filepath);
ObjectInputStream in = new ObjectInputStream(fileIn);
Object o=null;
o = in.readObject();
in.close();
fileIn.close();
return o;
}catch(IOException i){
i.printStackTrace();
return null;
}catch(ClassNotFoundException c){
System.out.println("Class not found");
c.printStackTrace();
return null;
}
}
对于transient
修饰的属性,因未被序列化,故在反序列化时并不会改变它的值。若有初始化,则保持不变;若无初始化,则为空指针null
。
版本控制
一个类在经过序列化之后,其内部组成可能有改变,这时就可以使用一个静态变量来控制兼容性问题。若改动较小,希望兼容老版本,则版本不变;若改动较大,无法兼容老版本,则更新版本号。
private static final long serialVersionUID = 1L;
如使用下面的这个类进行序列化
public class Test implements Serializable{
private static final long serialVersionUID = 1L;
private final String name;
private final int ID;
private final float age;
}
修改后,更改了属性类型及版本号:
public class Test implements Serializable{
private static final long serialVersionUID = 2L;
private final StringBuilder name;
private final long ID;
private final double age;
}
反序列化时就会报错:
java.io.InvalidClassException: Test; local class incompatible: stream classdesc serialVersionUID = 1, local class serialVersionUID = 2
通过序列化实现深度克隆
Java的深拷贝可由序列化与反序列化完成。
如以下方法,接受一个实例(可序列化的),返回一个深度克隆的实例。
public static <T extends Serializable> T Clone(T object){
T clone = null;
try{
ByteArrayOutputStream out = new ByteArrayOutputStream();
ObjectOutputStream objout = new ObjectOutputStream(out);
objout.writeObject(object);
objout.close();
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
ObjectInputStream objin = new ObjectInputStream(in);
clone = (T) objin.readObject();
objin.close();
}catch (Exception e){
e.printStackTrace();
}
return clone;
}
Comments