最近刚好在打java基础,但是用ObjectOutputStream进行反序列的时候
遇到了反序列化失败的问题,所以在这里讨论一下反序列化失败的问题
目录
1. 反序列化失败抛出的异常信息
java.io.InvalidClassException: javaSE.bean.Student; local class incompatible: stream classdesc serialVersionUID = -4563547663326088190, local class serialVersionUID = -27206582129922100
at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:699)
at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:2001)
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1848)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2158)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1665)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:501)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:459)
异常信息:
- local class incompatible
- 本地的类不匹配
- stream classdesc serialVersionUID = -4563547663326088190
- 在流中类的序列化版本号是-4563547663326088190
- local class serialVersionUID = -27206582129922100
- 本地类的序列化版本号是-27206582129922100
很明显抛出异常的原因就是:
本地类的序列化版本号和我们进行序列化时的序列化版本号不同
所以要搞清楚这个异常就要搞清楚序列化版本号是个什么东西
2. 在java中类的区分
1.serializable接口
我们在进行序列化的时候会让某个类去实现serializable接口
比如我有一个student类去实现serializable接口:
public class Student implements Serializable {
private int age;
private String name;
public Strudent(int age, String name){
this.age = age;
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Student{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
}
- serializable接口是一个标志接口,在类中我们并没有实现任何方法
- 实现serializable接口是因为jvm会自动分配一个序列化版本号
2.java中类的区分
- 第一,以类名来区分。类名不一样,肯定不是同一个类。
- 第二, 如果类名一样,靠序列化版本号区分。
3. 序列化版本号
读到这里你肯定对序列化版本号多少有点了解了,下面是对序列化版本号的具体说明
1.序列化版本号的作用
在讲清楚序列化版本号之前,我们来看个故事:
三年前小蓝在开发某个项目的时候写了一个学生类:
public class Student implements Serializable {
private int age;
private String name;
public Strudent(int age, String name){
this.age = age;
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Student{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
}
三年后,小蓝跑路了。来了个新人小红,由于业务需求,小红在小蓝写的类上增加了一个id属性
public class Student implements Serializable {
private int age;
private String name;
private int id;
public Student(int age, String name, int id) {
this.age = age;
this.name = name;
this.id = id;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@Override
public String toString() {
return "Student{" +
"age=" + age +
", name='" + name + '\'' +
", id=" + id +
'}';
}
}
当小红改完代码,重新运行程序时,项目跑不了,抛出的异常让小红懵逼了
java.io.InvalidClassException: javaSE.bean.Student; local class incompatible: stream classdesc serialVersionUID = -4563547663326088190, local class serialVersionUID = -27206582129922100
at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:699)
at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:2001)
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1848)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2158)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1665)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:501)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:459)
小红请教了她的师傅,师傅告诉她:加一个序列化版本号就行了
小红照做了:
public class Student implements Serializable {
private static final long serialVersionUID = 7471391170055841173L;
private int age;
private String name;
private int id;
public Student(int age, String name, int id) {
this.age = age;
this.name = name;
this.id = id;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@Override
public String toString() {
return "Student{" +
"age=" + age +
", name='" + name + '\'' +
", id=" + id +
'}';
}
}
小红运行了代码,成功地跑了起来,小红这才放心地下班了
- 可以看到一个继承了 serializable 接口的类如果不加上一个 serialVersionUID
用 ObjectInputStream 进行序列化之后,如果这个类有一点改变,反序列化就会失败 - 序列化版本号的作用就是告诉 java 虚拟机 改变之前的类和改变之后的类是同一个类
- 比如上面的例子:小蓝三年前写的 Student 和小红改的 Student 类是同一个类,但是却反序列化失败了
因为 Student 类继承了 serializable 接口,会自动分配一个 serialVersionUID。
但是因为小红把代码改了,在重新编译运行的时候,又会分配一个serialVersionUID
导致之前序列化时类的 serialVersionUID 和反序列化时类 serialVersionUID 所以就抛出了异常
2.手动创建序列化版本号
对于序列化版本号的并没有多大讲究,
在实现了serializable接口的类中添加一个serialVersionUID即可
只要保证在一个项目中序列化版本号不要重复就行
- 比如:
private static final long serialVersionUID = 1L;
- 又或者:
private static final long serialVersionUID = 7471391170055841173L;
- idea中可以选中类名按Alt + Enter即可自动生成serialVersionUID
4. 解决反序列化失败的方法
- 如果出现以下异常信息:
- 要把stream classdesc serialVersionUID复制到你序列化的那个类中
- 别忘了加private static final long
- 数字后面还要加一个L,表示long类型
- 比如
-
为了避免反序列化失败情况的发生,一个类实现了serializable接口就一定要添加一个serialVersionUID
-
比如: