上一篇《java序列化和反序列化(一)——概念及Demo分析》中了解到序列化和反序列化的一些基本概念,本篇着重讲一下关于序列化版本UID(即serialVersionUID)的一些问题
1. 一个疑问引发的思考
我们通常在实现 java.io.Serializable 接口时,会在实现类中加一个静态变量,类似下面这样(下面例子中的serialVersionUID是借助IDE自动生成)
private static final long serialVersionUID = -891097293389723583L;
为什么要加上这个变量呢?作用又是什么?下面借助一个例子来说明。
2. 举例
Student.java
public class Student implements Serializable {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
SerializableTest.java
public class SerializableTest {
/**
* Student序列化
*/
public static void serialize() {
Student student = new Student();
student.setAge(15);
student.setName("张三");
ObjectOutputStream ots = null;
try {
//指定java对象Person序列化后的字节流输出的目标流
ots = new ObjectOutputStream(new FileOutputStream("D:/studentStream.txt"));
//将Person序列化成的字节流写入到目标输出流(此处为文件输出流)中
ots.writeObject(student);
System.out.println("序列化成功!");
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (ots != null) {
ots.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* Student序列化字节的反序列化操作
*/
public static void deSerialize() {
ObjectInputStream inputStream = null;
try {
//指定序列化字节流的来源
inputStream = new ObjectInputStream(new FileInputStream("D:/studentStream.txt"));
//将字节流反序列化成Object对象(在这里进行了强转)
Student student = (Student) inputStream.readObject();
System.out.println("执行反序列化过程成功:" + student.getName() + "-" + student.getAge() + "岁");
} catch (ClassNotFoundException | IOException e) {
e.printStackTrace();
} finally {
try {
if (inputStream != null) {
inputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
serialize();
//deSerialize();
}
}
step1:
在SerializableTest.java的main方法中,反序列化方法: deSerialize() 暂时先注掉,第一步我们先仅执行序列化方法:serialize()
public static void main(String[] args) throws InterruptedException {
serialize();
//deSerialize();
}
执行成功后,仍可在"D:/studentStream.txt"路径下找到序列化后的字节流文件
step2:
在Student.java中的增加num属性
public class Student implements Serializable {
private String name;
private int age;
//新增属性num
private long num;
public long getNum() {
return num;
}
public void setNum(long num) {
this.num = num;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
step3:
在SerializableTest.java的main方法中,仅执行反序列化方法:deSerialize()
public static void main(String[] args) throws InterruptedException{
//serialize();
deSerialize();
}
执行结果如下
java.io.InvalidClassException: io.SerializableTest$Student;
local class incompatible:
stream classdesc serialVersionUID = -2732171979826434169,
local class serialVersionUID = -3422328592789288580
at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:699)
at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1885)
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1751)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2042)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1573)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:431)
at io.SerializableTest.deSerialize(SerializableTest.java:88)
at io.SerializableTest.main(SerializableTest.java:107)
结果分析
-
失败原因: 从执行结果来看,之所以报错,原因是:从我们step1执行后生成的 字节流文件(studentStream.txt) 反序列化后的Student对象中的serialVersionUID和刚刚 增加了num属性后的Student.java 中运行时生成的serialVersionUID不一致,故导致反序列化失败
-
结论
1) Java在运行时,jvm会自动为每个需要序列化且未指定serialVersionUID的实例化对象自动生成一个serialVersionUID2)java类如未显示指定serialVersionUID变量,则该类内的属性和方法每做一次变更,jvm便每次随机分配一个serialVersionUID。如上述Student类,在未加num属性时,序列化后时生成的serialVersionUID和增加num后生成的不同,所以才导致的反序列化失败。
3)java做反序列化操作时,是根据序列化版本UID去做对比和判断,对比一致则进入正常序列化操作,不一致则抛 java.io.InvalidClassException 异常
3. 基于上述的过程及结论我们探究指定serialVersionUID的作用
还是上述步骤,只不过这一次,我们在step1操作之前,先在Student.java中加上
private static final long serialVersionUID = 1L;
然后按上述中step1->step2->step3顺序执行
执行结果如下
我们可以看到,执行成功,也就是说:当我们显示指定serialVersionUID后,Student的序列化版本UID并没有随着属性和方法的变动而改变
结论
- Java的反序列化执行,先会对比字节流转换实例化对象后的serialVersionUID和当前类实例化对象中的serialVersionUID,如果一致,则序列化成功,不一致则抛异常
- 在需要序列化的实例中(实现了Serializable接口)显示指定serialVersionUID后,不论类中的属性和方法如何变动,在序列化和反序列化时,拿到的都是同一个serialVersionUID(被指定的那个),通过此,以保证同一类的实例化对象反序列化成功