Serializable接口中serialVersionUID的作用 示例

来源:互联网 发布:手机渲染视频软件 编辑:程序博客网 时间:2024/05/29 12:34

在Eclipse中,一个对象如果实现了Serializable接口,会有一个警告信息:

The serializable class Student does not declare a static final serialVersionUID field of type long

点击警告信息,会给出3个修复建议,其中第二个是:

add generated serial version id

点击此建议,就会自动生成如下代码(具体数值不定):

private static final long serialVersionUID = 6859324283664879676L;

这个值是根据类名、接口名、成员方法及属性等来生成一个64位的哈希字段。

那么这个常量serialVersionUID有什么作用呢?如果忽略这个警告信息,不定义这个常量又会如何?

先来明确一下对象序列化的含义和用途:

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

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

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

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

(2)  在网络上以字节序列的方式传输对象。 

我们以第一种用途为例,用具体代码测试serialVersionUID的作用

代码段一:类Student version1.0,这个类将会被序列化保存到文件中

/** * Student  * @version 1.0 */public class Student implements Serializable {private String name;private int age;public Student(String name, int age) {this.name = name;this.age = age;}@Overridepublic String toString() {return "姓名:" + name + "  年龄:" + age;}}

代码段二:方法write(序列化)

/** * 将一个对象数据写入到一个二进制文件 *  */private static void write(){try {File file = new File("student_object.dat");FileOutputStream fos = new FileOutputStream(file);ObjectOutputStream oos = new ObjectOutputStream(fos);Student std = new Student("Tom", 15);oos.writeObject(std);oos.close();} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}}
代码段三:方法read(反序列化)

/** * 从一个二进制文件中读取对象数据并打印对象信息 *  */private static void read(){try {File file = new File("student_object.dat");FileInputStream fis = new FileInputStream(file);ObjectInputStream ois = new ObjectInputStream(fis);Object obj = ois.readObject();System.out.println(obj);ois.close();} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();} catch (ClassNotFoundException e) {e.printStackTrace();}}
代码段四:调用

public static void main(String[] args) {write();read();}
运行结果:姓名:Tom  年龄:15

可以看到,在没有定义serialVersionUID这个常量的情况下,序列化和反序列化过程都没有问题。

但是,如果软件升级,Studen类进行了扩展,加入了一个变量:number(学号),如下

代码段五:类Student version2.0

/** * Student  * @version 2.0 */public class Student implements Serializable {private String name;private int age;private int number;public Student(String name, int age) {this.name = name;this.age = age;}public Student(String name, int age, int number) {this.name = name;this.age = age;this.number = number;}@Overridepublic String toString() {return "姓名:" + name + "  年龄:" + age + "  学号:" + number;}}
这个时候,再调用read方法读取之前保存的student_object.dat 文件,就会导致兼容性问题,因为要把旧版的Student反序列化成新版的Student,而新版中多了一个变量,就会抛出异常信息:

java.io.InvalidClassException: com.test.io.Student; local class incompatible: stream classdesc serialVersionUID = 6859324283664879676, local class serialVersionUID = -5807139062119555074

意思是从流中读取的类Student(v1.0)的serialVersionUID和本地类Student(v2.0)的serialVersionUID不一致。

好,这时候serialVersionUID终于出场了^_^

前面说过,这个值是根据类名、接口名、成员方法及属性等来生成一个64位的哈希字段。它其实起了一个在文件内部声明版本的作用,前面因为我们忽略了Eclipse的警告,没有定义这个常量serialVersionUID,所以,虚拟机会分别计算出stream中读取的类的serialVersionUID和本地对应的类的serialVersionUID,然后两者做对比,如果一致,则认为是同一版本,继续反序列化操作;如果不一致,则认为是不同版本,序列化过程可能会丢失文件信息,所以抛出java.io.InvalidClassException 异常。

那么如何避免此类问题呢?就是听从Eclipse的建议,在Student类中自己指定或者自动生成一个serialVersionUID,如下:

代码段:类Student version 1.1

/** * Student  * @version 1.1 */public class Student implements Serializable {private static final long serialVersionUID = 6859324283664879676L;private String name;private int age;public Student(String name, int age) {this.name = name;this.age = age;}@Overridepublic String toString() {return "姓名:" + name + "  年龄:" + age;}}

调用上面的write方法,将定义了serialVersionUID的Student写入文件中,稍后再用。

然后软件升级,Student类扩展,加入变量:number(学号),

同时:保持serialVersionUID的值不变

如下:

/** * Student  * @version 2.1 */public class Student implements Serializable {private static final long serialVersionUID = 6859324283664879676L;private String name;private int age;private int number;public Student(String name, int age) {this.name = name;this.age = age;}public Student(String name, int age, int number) {this.name = name;this.age = age;this.number = number;}@Overridepublic String toString() {return "姓名:" + name + "  年龄:" + age + "  学号:" + number;}}

然后调用之前的read方法,读取刚才生成的 Student v 1.1版的文件

运行结果:姓名:Tom  年龄:15  学号:0

测试结果表明,在定义了同一个serialVersionUID值之后,类做了扩展的情况下,依然保持了兼容性,能将老版本时生成的序列化文件反序列化为新版的类,只是其中新增的的变量为默认值

简言之:serialVersionUID 的作用就是保持兼容性。


扩展问题一:

在read方法中对文件进行反序列化操作的时候,java虚拟机怎么知道要把文件中的数据转换成Student类呢?还打印出了Student类的相关信息?

答案就在文件中,用EditPlus打开序列化文件 student_object.dat,选择“16进制查看器”,文件内容显示如下:


可以看到,文件中包含了包名、类名等信息,所以java虚拟机会按图索骥,找到相关的本地类,然后进行反序列化

如果随便用一个文本文件,改名为student_object.dat,然后读取,会导致转换失败,抛出IOException。


扩展问题二:

上面所说的“软件升级,类扩展”还有很多方式,比如:

1. 如果用2.1版的Student 写入文件,再用1.1版的Student 来读取,会出现什么结果?

2. 如果Student 类扩展的时候没有新增字段,而是修改了已有的变量名或变量类型,又会出现什么结果?

类似的可能还有很多种,有兴趣的朋友可以自己试试。

0 0
原创粉丝点击