Serializable兼容性问题及serialVersionUID的使用

兼容性问题 
兼容性历来是复杂而麻烦的问题。

不要兼容性:

      首先来看看如果我们的目的是不要兼容性,应该注意哪些。不要兼容性的场合很多,比如war3每当版本升级就不能够读取以前的replays。

      兼容也就是版本控制,java通过一个名为UID(stream unique identifier)来控制,这个UID是隐式的,它通过类名,方法名等诸多因素经过计算而得,理论上是一一映射的关系,也就是唯一的。如果UID不一样的话,就无法实现反序列化了,并且将会得到InvalidClassException。

      当我们要人为的产生一个新的版本(实现并没有改动),而抛弃以前的版本的话,可以通过显式的声名UID来实现:

      private static final long serialVersionUID=1l;

你可以编造一个版本号,但注意不要重复。这样在反序列化的时候老版本将得到InvalidClassException,我们可以在老版本的地方捕捉这个异常,并提示用户升级的新的版本。

当改动不大时,保持兼容性(向下兼容性的一个特例):

      有时候你的类增加了一些无关紧要的非私有方法,而逻辑字段并不改变的时候,你当然希望老版本和新版本保持兼容性,方法同样是通过显式的声名UID来实现。下面我们验证一下。

      老版本:

import java.io.*;

public class Serial implements Serializable {

      int company_id;

      String company_addr;

         public Serial1(int company_id, String company_addr) { 
             this.company_id = company_id; 
             this.company_addr = company_addr; 
      }

public String toString() {

          return "DATA: "+company_id+" "+

company_addr;

      }

}

      新版本

import java.io.*;

public class Serial implements Serializable {

      int company_id;

      String company_addr;

         public Serial1(int company_id, String company_addr) { 
             this.company_id = company_id; 
             this.company_addr = company_addr; 
      }

public String toString() {

          return "DATA: "+company_id+" "+ company_addr;

      }

      public void todo(){}//无关紧要的方法

}

首先将老版本序列化,然后用新版本读出,发生错误:

java.io.InvalidClassException: Serial.Serial1; local class incompatible: stream classdesc serialVersionUID = 762508508425139227, local class serialVersionUID = 1187169935661445676

接下来我们加入显式的声名UID:

private static final long serialVersionUID=762508508425139227l;

再次运行,顺利地产生新对象

DATA: 1001 com1

如何保持向上兼容性:

      向上兼容性是指老的版本能够读取新的版本序列化的数据流。常常出现在我们的服务器的数据更新了,仍然希望老的客户端能够支持反序列化新的数据流,直到其更新到新的版本。可以说,这是半自动的事情。

      跟一般的讲,因为在java中serialVersionUID是唯一控制着能否反序列化成功的标志,只要这个值不一样,就无法反序列化成功。但只要这个值相同,无论如何都将反序列化,在这个过程中,对于向上兼容性,新数据流中的多余的内容将会被忽略;对于向下兼容性而言,旧的数据流中所包含的所有内容都将会被恢复,新版本的类中没有涉及到的部分将保持默认值。利用这一特性,可以说,只要我们认为的保持serialVersionUID不变,向上兼容性是自动实现的。

      当然,一但我们将新版本中的老的内容拿掉,情况就不同了,即使UID保持不变,会引发异常。正是因为这一点,我们要牢记一个类一旦实现了序列化又要保持向上下兼容性,就不可以随随便便的修改了!!!

      测试也证明了这一点,有兴趣的读者可以自己试一试。

如何保持向下兼容性:

         一如上文所指出的,你会想当然的认为只要保持serialVersionUID不变,向下兼容性是自动实现的。但实际上,向下兼容要复杂一些。这是因为,我们必须要对那些没有初始化的字段负责。要保证它们能被使用。

      所以必须要利用 
      private void readObject(java.io.ObjectInputStream in) 
      throws IOException, ClassNotFoundException{ 
         in.defaultReadObject();//先反序列化对象 
         if(ver=5552){//以前的版本5552 
             …初始化其他字段 
          }else if(ver=5550){//以前的版本5550 
            …初始化其他字段 
          }else{//太老的版本不支持 
          throw new InvalidClassException(); 
      } 

      细心的读者会注意到要保证in.defaultReadObject();能够顺利执行,就必须要求serialVersionUID保持一致,所以这里的ver不能够利用serialVersionUID了。这里的ver是一个我们预先安插好的final long ver=xxxx;并且它不能够被transient修饰。所以保持向下的兼容性至少有三点要求:

           1.serialVersionUID保持一致
           2.预先安插好我们自己的版本识别标志的final long ver=xxxx; 
           3.保证初始化所有的域

讨论一下兼容性策略:

         到这里我们可以看到要保持向下的兼容性很麻烦。而且随着版本数目的增加。维护会变得困难而繁琐。讨论什么样的程序应该使用怎么样的兼容性序列化策略已经超出本文的范畴,但是对于一个游戏的存盘功能,和对于一个字处理软件的文档的兼容性的要求肯定不同。对于rpg游戏的存盘功能,一般要求能够保持向下兼容,这里如果使用java序列化的方法,则可根据以上分析的三点进行准备。对于这样的情况使用对象序列化方法还是可以应付的。对于一个字处理软件的文档的兼容性要求颇高,一般情况下的策略都是要求良好的向下兼容性,和尽可能的向上兼容性。则一般不会使用对象序列化技术,一个精心设计的文档结构,更能解决问题。

ps:serialVersionUID做为序列化的版本控制是一个非常有用的兼容手段,通常情况下,我们应该手工设置该值,当然,ide eclipse有提示你设置其值。serialVersionUID可以任意设置,根据不同的兼容性做相应改动。

阅读更多
换一批

没有更多推荐了,返回首页