serialVersionUID作用:
序列化时为了保持版本的兼容性,即在版本升级时反序列化仍保持对象的唯一性。
有两种生成方式:
一个是默认的1L,比如:private static final long serialVersionUID = 1L;
一个是根据类名、接口名、成员方法及属性等来生成一个64位的哈希字段,比如:
private static final long serialVersionUID = xxxxL;
关于其定义,可参考JDK文档:http://download.oracle.com/javase/1.5.0/docs/api/java/io/Serializable.html
在Eclipse中,提供两种方式让我们快速添加SerialVersionUid。
add default serial version ID:
Adds a default serial version ID to the selected type
Use this option to add a user-defined ID in combination with custom serialization code if the type did undergo structural change since its first release.
add generated serial version ID:
Adds a generated serial version ID to the selected type
Use this option to add a compiler-generated ID if the type didnot undergo structural change since its first release.
一种就是1L,一种是生成一个很大的数,这两种有什么区别呢?
看上去,好像每个类的这个类不同,似乎这个SerialVersionUid在类之间有某种关联。其实不然,两种都可以,从JDK文档也看不出这一点。我们只要保证在同一个类中,不同版本根据兼容需要,是否更改SerialVersionUid即可。
对于第一种,需要了解哪些情况是可兼容的,哪些根本就不兼容。 参考文档:http://java.sun.com/j2se/1.4/pdf/serial-spec.pdf
在可兼容的前提下,可以保留旧版本号,如果不兼容,或者想让它不兼容,就手工递增版本号。
1->2->3.....
第二种方式,是根据类的结构产生的hash值。增减一个属性、方法等,都可能导致这个值产生变化。我想这种方式适用于这样的场景:
开发者认为每次修改类后就需要生成新的版本号,不想向下兼容,操作就是删除原有serialVesionUid声明语句,再自动生成一下。
个人认为,一般采用第一种就行了,简单。第二种能够保证每次更改类结构后改变版本号,但还是要手工去生成,并不是修改了类,会提示你要去更新这个SerialVersionUid,所以虽然看上去很cool,实际上让人很迷惑。
参考:
1.一篇较好的关于serialVesionUid的说明:
http://www.mkyong.com/java-best-practices/understand-the-serialversionuid/
serialVersionUID的使用
先来看一个例子:
定义一个bean:- public class Serial implements Serializable {
- int id;
- String name;
- public Serial(int id, String name) {
- this.id = id;
- this.name = name;
- }
- public String toString() {
- return "DATA: " + id + " " +name;
- }
- }
序列化操作:
- public static void main(String[] args) {
- Serial serial=new Serial(1,"hrbeu");
- System.out.println("object serial:"+serial);
- try{
- FileOutputStream fos=new FileOutputStream("serialTest.txt");
- ObjectOutputStream oos=new ObjectOutputStream(fos);
- oos.writeObject(serial);
- oos.flush();
- oos.close();
- }catch(Exception e){
- System.out.println("Exception:"+e);
- }
- }
反序列化操作:
- public static void main(String[] args) {
- try{
- Serial object2;
- FileInputStream fis=new FileInputStream("serialTest.txt");
- ObjectInputStream ois=new ObjectInputStream(fis);
- object2=(Serial)ois.readObject();
- ois.close();
- System.out.println("object deserial:"+object2);
- }catch(Exception e){
- System.out.println("Exception:"+e);
- }
- }
运行程序,则反序列化成功。
现在改动一下bean,在定义的bean中添加一个公有方法(非私有就可以):
- public void todo(){}//没什么意义的方法
接下来在老版本的序列化的结果上反序列化就会出错:
- Exception:java.io.InvalidClassException: com.serializable.test.Serial; local class incompatible: stream classdesc serialVersionUID = 5087256472645325817, local class serialVersionUID = 6553118832574415117
接下来在定义的bean中显示的声明UID,如下:
- private static final long serialVersionUID = 6553118832574415117L;
再次重新执行上面的步骤,则反序列化成功。
有时候你的类增加了一些无关紧要的非私有方法,而逻辑字段并不改变的时候,你希望老版本和新版本保持兼容性,则需要显式的声名UID来实现。
以下内容来自网络:
====================================================================
如何保持向上兼容性:
向上兼容性是指老的版本能够读取新的版本序列化的数据流。常常出现在我们的服务器的数据更新了,仍然希望老的客户端能够支持反序列化新的数据流,直到其更新到新的版本。可以说,这是半自动的事情。
跟一般的讲,因为在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序列化的方法,则可根据以上分析的三点进行准备。对于这样的情况使用对象序列化方法还是可以应付的。对于一个字处理软件的文档的兼容性要求颇高,一般情况下的策略都是要求良好的向下兼容性,和尽可能的向上兼容性。则一般不会使用对象序列化技术,一个精心设计的文档结构,更能解决问题。
package 文件;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
//序列化:为了保存在内存中的各种对象的状态,并且可以把保存的对象状态再读出来。虽然你可以用你自己的各种各样的方法来保存Object States
//a)当你想把的内存中的对象保存到一个文件中或者数据库中时候;
//b)当你想用套接字在网络上传送对象的时候;
//c)当你想通过RMI传输对象的时候;
public class 序列化和反序列化 implements Serializable {
//存储变量和值,不存储静态成员方法、变量
/**
*
*/
private static final long serialVersionUID = 2038878508354455668L;
private int i;
public 序列化和反序列化() {
i=3;
}
public String get(){
return this.getClass().getPackage().getName();
}
//开始执行一个程序前,并没有创建main()方法所在类的实例对象,它只能通过类名 类调用主方法main()作为程序入口,所以该方法是static
//因为main()方法是由Java虚拟机调用的,所以必须是public
static void 序列化(){
序列化和反序列化 a=new 序列化和反序列化();
String s=System.getProperty("user.dir")+"\\src\\"+a.get()+"\\a.txt";//得到当前文件绝对路径
try {
FileOutputStream fos= new FileOutputStream(s);
ObjectOutputStream oos=new ObjectOutputStream(fos);
oos.writeObject(a);
oos.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
static void 反序列化(){
序列化和反序列化 b =new 序列化和反序列化();
String s=System.getProperty("user.dir")+"\\src\\"+b.get()+"\\a.txt";//得到当前文件绝对路径
try {
FileInputStream fis=new FileInputStream(s);
ObjectInputStream ois=new ObjectInputStream(fis);
b=(序列化和反序列化) ois.readObject();
System.out.println(b.i);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public static void main(String[] args){
反序列化();
}
}