serialVersionUID的作用

来源:互联网 发布:win2008r2装sql 编辑:程序博客网 时间:2024/04/29 18:11

简单来说,Java的序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体(类)的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常。(InvalidCastException)

serialVersionUID有两种显示的生成方式:

         

1.当采用Add default Serial version ID修复时,eclipse会加上:

private static final long serialVersionUID = 1L; 

2.当采用Add generated Serial version ID修复时, eclipse根据类名、接口名、成员方法及属性等来生成一个64位的哈希字段,比如:

     private static final long serialVersionUID =1977402643848374753L

当你一个类实现了Serializable接口,如果没有显示的定义serialVersionUID,Eclipse会提供这个提示功能告诉你去定义 。在Eclipse中点击类中warning的图标一下,Eclipse就会自动给定两种生成的方式。如果不想定义它,在Eclipse的设置中也可以把它关掉的,设置如下:Window ==> Preferences ==> Java ==> Compiler ==> Error/Warnings ==>Potential programming problems将Serializable class without serialVersionUID的warning改成ignore即可。
当实现java.io.Serializable接口的实体(类)没有显式地定义一个名为serialVersionUID,类型为long的变量时,Java序列化机制会根据编译的class(它通过类名,方法名等诸多因素经过计算而得,理论上是一一映射的关系,也就是唯一的)自动生成一个serialVersionUID作序列化版本比较用,这种情况下,如果class文件(类名,方法明等)没有发生变化(增加空格,换行,增加注释,等等),就算再编译多次,serialVersionUID也不会变化的.

如果我们不希望通过编译来强制划分软件版本,即实现序列化接口的实体能够兼容先前版本,未作更改的类,就需要显式地定义一个名为serialVersionUID,类型为long的变量,不修改这个变量值的序列化实体都可以相互进行串行化和反串行化。

如果你没有考虑到兼容性问题时,就把它关掉,不过有这个功能是好的,只要任何类别实现了Serializable这个接口的话,如果没有加入 serialVersionUID,Eclipse都会给你warning提示,这个serialVersionUID为了让该类别 Serializable向后兼容。


问题一:假设实体类serialVersionUID不一致,序列化和反序列化过程会产生什么错误呢?

问题二:假设实体类serialVersionUID不变,如果实体类增加一个字段序列化和反序列化过程有什么影响呢?

问题三:假设实体类serialVersionUID不变,如果实体类减少一个字段序列化和反序列化过程有什么影响呢?

问题四:假设实体类没写serialVersionUID,修改类和不修改类对序列化和反序列化过程有什么影响呢?


测试如下:

一个实体类Serial,一个序列化类和一个反序列化类.

实体类:在本例中,在测试类SerialT执行序列化,然后,在测试类Deserial执行反序列化.

<span style="font-size:18px;">package com.aaron.bo;import java.io.Serializable;public class Serial implements Serializable {<span style="white-space:pre"></span>/** * */<span style="white-space:pre"></span>private static final long serialVersionUID = 6977402643848374753L;<span style="white-space:pre"></span>int id;<span style="white-space:pre"></span>String name;<span style="white-space:pre"></span>public Serial(int id, String name) {<span style="white-space:pre"></span>this.id = id;<span style="white-space:pre"></span>this.name = name;<span style="white-space:pre"></span>}<span style="white-space:pre"></span>public String toString() {<span style="white-space:pre"></span>return "DATA: " + id + " " + name;<span style="white-space:pre"></span>}}</span>

序列化SerialT

<span style="font-size:18px;">package com.aaron.bo;import java.io.FileNotFoundException;import java.io.FileOutputStream;import java.io.IOException;import java.io.ObjectOutputStream;public class SerialT {public static void main(String[] args) {Serial serial1 = new Serial(1, "song");System.out.println("Object Serial" + serial1);try {FileOutputStream fos = new FileOutputStream("serialTest.txt");ObjectOutputStream oos = new ObjectOutputStream(fos);oos.writeObject(serial1);oos.flush();oos.close();} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}}}</span>

反序列化DeserialT

<span style="font-size:18px;">package com.aaron.bo;import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.IOException;import java.io.ObjectInputStream;public class DeserialT {<span style="white-space:pre"></span>public static void main(String[] args) {<span style="white-space:pre"></span>Serial serial2;<span style="white-space:pre"></span>try {<span style="white-space:pre"></span>FileInputStream fis = new FileInputStream("serialTest.txt");<span style="white-space:pre"></span>ObjectInputStream ois = new ObjectInputStream(fis);<span style="white-space:pre"></span>serial2 = (Serial) ois.readObject();<span style="white-space:pre"></span>ois.close();<span style="white-space:pre"></span>System.out.println("Object Deserial---" + serial2);<span style="white-space:pre"></span>} catch (FileNotFoundException e) {<span style="white-space:pre"></span>e.printStackTrace();<span style="white-space:pre"></span>} catch (ClassNotFoundException e) {<span style="white-space:pre"></span>e.printStackTrace();<span style="white-space:pre"></span>} catch (IOException e) {<span style="white-space:pre"></span>e.printStackTrace();<span style="white-space:pre"></span>} <span style="white-space:pre"></span>}}</span>

问题一:假设实体类serialVersionUID不一致,会产生什么错误呢?

测试过程:先执行SerialT,再修改serialVersionUID的值,然后执行DeserialT,有如下报错

java.io.InvalidClassException: com.aaron.bo.Serial; local class incompatible: stream classdesc serialVersionUID = 6977402643848374753, local class serialVersionUID = 1977402643848374753at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:560)at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1580)at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1493)at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1729)at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1326)at java.io.ObjectInputStream.readObject(ObjectInputStream.java:348)at com.aaron.bo.DeserialT.main(DeserialT.java:15)

问题二:假设实体类serialVersionUID不变,如果实体类增加一个字段序列化和反序列化过程有什么影响呢?

测试过程:先执行SerialT,然后在实体类中添加age字段(如下所示),

package com.aaron.bo;import java.io.Serializable;public class Serial implements Serializable {private static final long serialVersionUID = 1977402643848374753L;private int id;private int age;private String name;public Serial(int id, int age, String name) {this.id = id;this.age = age;this.name = name;}public String toString() {return "DATA: " + id + " " + age + " " + name;}}

再执行DeserialT,结果如下:
Object Deserial---DATA: 1 0 song      (age赋给了默认值)


问题三:假设实体类serialVersionUID不变,如果实体类减少一个字段对序列化和反序列化过程有什么影响呢?
测试过程:使用问题二中的实体类,修改SerialT中的代码(如下))
Serial serial1 = new Serial(1,1, "song");
然后执行SerialT,再删除实体类中age字段,还原为问题一的实体类,然后执行DeSerialT,结果如下:
Object Deserial---DATA: 1 song

NOTE:由问题二三可见serialVersionUID不变的话,对实体类增删字段不会报错。


问题四:假设实体类没写serialVersionUID,修改类对序列化和反序列化过程有什么影响呢?

测试过程:使用问题一中实体类,注释掉serialVersionUID,然后执行SerialT,修改实体类为问题二中的实体类,同时注掉serialVersionUID,再执行DeSerialT,结果如下:
java.io.InvalidClassException: com.aaron.bo.Serial; local class incompatible: stream classdesc serialVersionUID = -98624895101352597, local class serialVersionUID = -7107710563537436307at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:560)at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1580)at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1493)at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1729)at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1326)at java.io.ObjectInputStream.readObject(ObjectInputStream.java:348)at com.aaron.bo.DeserialT.main(DeserialT.java:15)


1.如果不显式设置SerialVersionUid,有什么后果?

jdk文档中有解释,建议我们显式声明,因为如果不声明,JVM会为我们自动产生一个值,但这个值和编译器的实现相关,并不稳定,这样就可能在不同JVM环境下出现反序列化时报InvalidClassException异常。

...it is strongly recommended that all serializable classes explicitly declare serialVersionUID values, since the default serialVersionUID computation is highly sensitive to class details that may vary depending on compiler implementations...

2.两种SerialVersionUid有什么区别?

在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.

对于第一种,

在可兼容的前提下,可以保留旧版本号,如果不兼容,或者想让它不兼容,就手工递增版本号。

第二种方式,是根据类结构产生的hash值。增减一的个属性、方法等,都可能导致这个值产生变化。我想这种方式适用于这样的场景:

开发者认为每次修改类后就需要生成新的版本号,不想向下兼容,操作就是删除原有serialVesionUid声明语句,再自动生成一下。

个人认为,一般采用第一种就行了,简单。第二种能够保证每次更改类结构后改变版本号,但需手工生成UID,按需求定吧。

 

补充:

当两个进程在进行远程通信时,彼此可以发送各种类型的数据。无论是何种类型的数据,都会以二进制序列的形式在网络上传送。发送方需要把这个Java对象转换为字节序列,才能在网络上传送;接收方则需要把字节序列再恢复为Java对象。

  把Java对象转换为字节序列的过程称为对象的序列化。

  把字节序列恢复为Java对象的过程称为对象的反序列化。

  对象的序列化主要有两种用途:

  1) 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中;

  2) 在网络上传送对象的字节序列。

java.io.ObjectOutputStream代表对象输出流,它的writeObject(Object obj)方法可对参数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中。

java.io.ObjectInputStream代表对象输入流,它的readObject()方法从一个源输入流中读取字节序列,再把它们反序列化为一个对象,并将其返回。

只有实现了Serializable或Externalizable接口的类的对象才能被序列化。Externalizable接口继承自Serializable接口,实现Externalizable接口的类完全由自身来控制序列化的行为,而仅实现Serializable接口的类可以采用默认的序列化方式。凡是实现Serializable接口的类都有一个表示序列化版本标识符的静态变量:private static final long serialVersionUID; 类的serialVersionUID的默认值完全依赖于Java编译器的实现,对于同一个类,用不同的Java编译器编译,有可能会导致不同的serialVersionUID,也有可能相同。为了提高serialVersionUID的独立性和确定性,强烈建议在一个可序列化类中显示的定义serialVersionUID,为它赋予明确的值。显式地定义serialVersionUID有两种用途:

  1)在某些场合,希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有相同的serialVersionUID;在某些场合,不希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有不同的serialVersionUID。

  2)当你序列化了一个类实例后,希望更改一个字段或添加一个字段,不设置serialVersionUID,所做的任何更改都将导致无法反序化旧有实例,并在反序列化时抛出一个异常。如果你添加了serialVersionUID,在反序列旧有实例时,新添加或更改的字段值将设为初始化值(对象为null,基本类型为相应的初始默认值),字段被删除将不设置。


参考:

1.一篇较好的关于serialVesionUid的说明:

http://www.mkyong.com/java-best-practices/understand-the-serialversionuid/

2.serialVesionUid相关讨论

http://stackoverflow.com/questions/888335/why-generate-long-serialversionuid-instead-of-a-simple-1l

3.compiler-generated ID生成算法

http://java.sun.com/javase/6/docs/platform/serialization/spec/class.html#4100

4.eclipse两种serialVersionUID区别

http://blog.csdn.net/dancen/article/details/7236575

5.补充

http://www.xuebuyuan.com/1430301.html


0 0