版本管理
如果使用序列化来保存对象,就需要考虑在程序演化时会有什么问题。例如,1.1 版本可以读入旧文件吗?仍旧使用1.0 版本的用户可以读入新版本产生的文件吗?显然,如果对象文件可以处理类的演化问题,那它正是我们想要的。
无论类的定义产生了什么样的变化,它的SHA指纹也会跟着变化,而我们都知道对象流将拒绝读入具有不同指纹的对象。但是,类可以表明它对其它早期版本保持兼容,要想这样做,就必须首先获得这个类的早期版本的指纹。我们可以使用JDK 中的单机程序 serialver 来获得这个数字,例如:
//Java文件所在目录打开命令行
serialver Orientation
//结果:
Orientation: private static final long serialVersionUID = 7148224725255643273L;
这个类的所有较新的版本都必须把serialVersionUID常量定义为与最初版本的指纹相同。
如果一个类具有名为serialVersionUID 的静态数据成员,它就不需要再人工地计算其指纹,而只需直接使用这个值。
对象流会将这个类当前版本的数据域与流中版本的数据域进行比较,当然,对象流只会考虑非瞬时和非静态的数据域。如果这两个数据域之间名字匹配而类型不匹配,那么对象流不会尝试将一种类型转换成另一种类型,因为这两个对象不兼容;如果流中的对象具有在当前版本中所没有的数据域,那么对象流会忽略这些额外的数据;如果当前版本具有在流中对象所没有的数据域,那么这些新添加的域将被设置成它们的默认值(如果是对象则为null,如果是数字则为0,如果是boolean 值则是 false)。
import java.io.*;
class Employee implements Serializable{
private static final long serialVersionUID = 7664196245018033257L; //必不可少
public String name = "world";
public int age = 20;
//版本2.0新增域
public int sex;
}
public class EmployeeTest{
public static void main(String[] args) throws Exception{
Employee ee = new Employee();
ee.name = "hello";
ee.age = 10;
//保存版本1.0对象
//ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("employee.dat"));
//out.writeObject(ee);
//读回
ObjectInputStream in = new ObjectInputStream(new FileInputStream("employee.dat"));
ee = (Employee)in.readObject();
System.out.println(ee.name+"--"+ee.age+"--"+ee.sex);
}
}
/*
运行结果:hello--10--0
分析:当少了serialVersionUID或者与流中的serialVersionUID不一致时,会出现运行时Exception in thread "main" java.io.InvalidClassException: Employee; local class incompatible: stream classdesc serialVersionUID = 7664196245018033257, local cla
ss serialVersionUID = -594094058233947184
*/
类的设计者能够在readObject 方法中实现额外的代码去订正版本不兼容问题,或者确保所有的方法在处理null数据时能足够健壮
克隆
序列机制提供了一种很有趣的用法:即提供了一种克隆对象的简便途径,只要对应的类是可序列化的即可。其做法很简单:直接将对象序列化到输出流中,然后将其读回。这样产生的新对象是对现有对象的一个深拷贝。在此过程中,我们不必将对象写出到文件中,因为可以用ByteArrayOutputStream将数据保存到字节数组中。
//添加如下类,Employee 继承该类
class SerialCloneable implements Cloneable,Serializable{
public Object clone(){
try{
//保存对象到字节数组
ByteArrayOutputStream bout = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(bout);
out.writeObject(this);
out.close();
//从字节数组读回复制对象
ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());
ObjectInputStream in = new ObjectInputStream(bin);
Object ret = in.readObject();
in.close();
return ret;
}catch(Exception e){
return null;
}
}
}
//在main()中添加如下测试代码
Employee ee1 = (Employee)new Employee().clone();
System.out.println(ee1.name+"--"+ee1.age+"--"+ee1.sex);
/*
运行结果;world--20--0
*/
我们应当当心这个方法,尽管它很灵巧,但是它通常会比显示地构建新对象并复制或克隆数据域的克隆方法慢的多。
以上内容和部分代码来源于Java 核心技术卷Ⅱ (原书第九版)