天天看点

《Java核心技术 卷Ⅱ 高级特性(原书第10版)》一2.4.5 版本管理

如果使用序列化来保存对象,就需要考虑在程序演化时会有什么问题。例如,1.1版本可以读入旧文件吗?仍旧使用1.0版本的用户可以读入新版本产生的文件吗?显然,如果对象文件可以处理类的演化问题,那它正是我们想要的。

乍一看,这好像是不可能的。无论类的定义产生了什么样的变化,它的SHA指纹也会跟着变化,而我们都知道对象输入流将拒绝读入具有不同指纹的对象。但是,类可以表明它对其早期版本保持兼容,要想这样做,就必须首先获得这个类的早期版本的指纹。我们可以使用JDK中的单机程序serialver来获得这个数字,例如,运行下面的命令

《Java核心技术 卷Ⅱ 高级特性(原书第10版)》一2.4.5 版本管理

如果在运行serialver程序时添加-show选项,那么这个程序就会产生下面的图形化对话框(参见图2-7)。

《Java核心技术 卷Ⅱ 高级特性(原书第10版)》一2.4.5 版本管理

这个类的所有较新的版本都必须把serialVersionUID常量定义为与最初版本的指纹相同。

《Java核心技术 卷Ⅱ 高级特性(原书第10版)》一2.4.5 版本管理

如果一个类具有名为serialVersionUID的静态数据成员,它就不再需要人工地计算其指纹,而只需直接使用这个值。

一旦这个静态数据成员被置于某个类的内部,那么序列化系统就可以读入这个类的对象的不同版本。

如果这个类只有方法产生了变化,那么在读入新对象数据时是不会有任何问题的。但是,如果数据域产生了变化,那么就可能会有问题。例如,旧文件对象可能比程序中的对象具有更多或更少的数据域,或者数据域的类型可能有所不同。在这些情况中,对象输入流将尽力将流对象转换成这个类当前的版本。

对象输入流会将这个类当前版本的数据域与被序列化的版本中的数据域进行比较,当然,对象流只会考虑非瞬时和非静态的数据域。如果这两部分数据域之间名字匹配而类型不匹配,那么对象输入流不会尝试将一种类型转换成另一种类型,因为这两个对象不兼容;如果被序列化的对象具有在当前版本中所没有的数据域,那么对象输入流会忽略这些额外的数据;如果当前版本具有在被序列化的对象中所没有的数据域,那么这些新添加的域将被设置成它们的默认值(如果是对象则是null,如果是数字则为0,如果是boolean值则是false)。

下面是一个示例:假设我们已经用雇员类的最初版本(1.0)在磁盘上保存了大量的雇员记录,现在我们在Employee类中添加了称为department的数据域,从而将其演化到了2.0版本。图2-8展示了将1.0版的对象读入到使用2.0版对象的程序中的情形,可以看到department域被设置成了null。图2-9展示了相反的情况:一个使用1.0版对象的程序读入了2.0版的对象,可以看到额外的department域被忽略。

《Java核心技术 卷Ⅱ 高级特性(原书第10版)》一2.4.5 版本管理

这种处理是安全的吗?视情况而定。丢掉数据域看起来是无害的,因为接收者仍旧拥有它知道如何处理的所有数据,但是将数据域设置为null却有可能并不那么安全。许多类都费尽心思地在其所有的构造器中将所有的数据域都初始化为非null的值,以使得其各个方法都不必去处理null数据。因此,这个问题取决于类的设计者是否能够在readObject方法中实现额外的代码去订正版本不兼容问题,或者是否能够确保所有的方法在处理null数据时都足够健壮。