共计 7658 个字符,预计需要花费 20 分钟才能阅读完成。
失常不设置 serialVersionUID 的序列化和反序列化
先定义一个实体 Student.class
, 须要实现 Serializable
接口,然而不须要实现 get(),set()办法
import java.io.Serializable;
public class Student implements Serializable {
private int age;
private String name;
public Student(int age, String name) {
this.age = age;
this.name = name;
}
@Override
public String toString() {
return "Student{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
}
测试类,思路是先把 Student 对象序列化到 Student.txt
文件,而后再讲 Student.txt
文件反序列化成对象,输入。
public class SerialTest {public static void main(String[] args) {serial();
deserial();}
// 序列化
private static void serial(){Student student = new Student(9, "Mike");
try {FileOutputStream fileOutputStream = new FileOutputStream("Student.txt");
ObjectOutputStream objectOutputStream= new ObjectOutputStream(fileOutputStream);
objectOutputStream.writeObject(student);
objectOutputStream.flush();} catch (Exception exception) {exception.printStackTrace();
}
}
// 反序列化
private static void deserial() {
try {FileInputStream fis = new FileInputStream("Student.txt");
ObjectInputStream ois = new ObjectInputStream(fis);
Student student = (Student) ois.readObject();
ois.close();
System.out.println(student.toString());
} catch (IOException | ClassNotFoundException e) {e.printStackTrace();
}
}
}
输入后果,序列化文件咱们能够看到Student.txt
, 反序列化进去,外面的字段都是不变的,阐明反序列化胜利了。
序列化之后,类文件减少了字段,反序列化会怎么样?
先说后果,会失败!!!
咱们在 Student.java
中减少了一个属性 score
, 从新生成了toString()
办法。
package com.aphysia.normal;
import java.io.Serializable;
public class Student implements Serializable {
private int age;
private String name;
private int score;
public Student(int age, String name) {
this.age = age;
this.name = name;
}
@Override
public String toString() {
return "Student{" +
"age=" + age +
", name='" + name + '\'' +
", score=" + score +
'}';
}
}
而后从新调用 deserial()
办法,会报错:
java.io.InvalidClassException: com.aphysia.normal.Student; local class incompatible: stream classdesc serialVersionUID = 7488921480006384819, local class serialVersionUID = 6126416635811747983
at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:699)
at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1963)
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1829)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2120)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1646)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:482)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:440)
at com.aphysia.normal.SerialTest.deserial(SerialTest.java:26)
at com.aphysia.normal.SerialTest.main(SerialTest.java:7)
从下面的报错信息中,咱们能够看到,类型不匹配,次要是因为 serialVersionUID
变动了!!!
????♂️????♂️ 发问环节:我都没有设置serialVersionUID
, 怎么变动的???小小的脑袋很多问号????????
正是因为没有设置,所以变动了,因为咱们减少了一个字段 score
, 如果咱们不设置serialVersionUID
,零碎就会主动生成,主动生成有危险,就是咱们的字段类型或者长度扭转(新增或者删除的时候),主动生成的serialVersionUID
会发生变化,那么以前序列化进去的对象,反序列化的时候就会失败。
实测:序列化实现之后,如果原类型字段缩小,不指定 serialVersionUID
的状况下,也是会报不统一的谬误。
《阿里巴巴 Java 开发手册》中规定,在兼容性降级中,在批改类的时候,不要批改 serialVersionUID 的起因。除非是齐全不兼容的两个版本。所以,serialVersionUID 其实是验证版本一致性的。
指定serialVersionUID
, 缩小或者减少字段会产生什么?
咱们生成一个serialVersionUID
, 办法:https://blog.csdn.net/Aphysia…。
private static final long serialVersionUID = 7488921480006384819L;
而后执行序列化,序列化出文件 Student.txt
后,减少一个字段 score
,执行反序列化。
是能够胜利的!!!只是新增的字段是默认值 0。
所以今后思考到迭代的问题的时候,个别可能减少字段或者缩小字段,都是须要思考兼容问题的,所以最好是本人指定serialVersionUID
,而不是由零碎主动生成。主动生成的,因为类文件变动,它也会发生变化,就会呈现不统一的问题,导致反序列化失败。
实测:如果我缩小了字段,只有指定了serialVersionUID
,也不会报错!!!
serialVersionUID 生成以及作用?
serialVersionUID
是为了兼容不同版本的,在 JDK 中,能够利用 JDK 的 bin
目录下的 serialver.exe
工具产生这个serialVersionUID
,对于Student.class
,执行命令:serialver Student
。
IDEA 生成实际上也是调用这个命令,代码调用能够这样写:
ObjectStreamClass c = ObjectStreamClass.lookup(Student.class);
long serialID = c.getSerialVersionUID();
System.out.println(serialID);
如果不显示的指定,那么不同 JVM 之间的移植可能也会出错,因为不同的编译器,计算这个值的策略可能不同,计算类没有批改,也会呈现不统一的问题。getSerialVersionUID()
源码如下:
public long getSerialVersionUID() {
// REMIND: synchronize instead of relying on volatile?
if (suid == null) {
suid = AccessController.doPrivileged(new PrivilegedAction<Long>() {public Long run() {return computeDefaultSUID(cl);
}
}
);
}
return suid.longValue();}
能够看到下面是应用了一个外部类的形式,应用特权计算computeDefaultSUID()
:
private static long computeDefaultSUID(Class<?> cl) {
// 代理
if (!Serializable.class.isAssignableFrom(cl) || Proxy.isProxyClass(cl))
{return 0L;}
try {ByteArrayOutputStream bout = new ByteArrayOutputStream();
DataOutputStream dout = new DataOutputStream(bout);
// 类名
dout.writeUTF(cl.getName());
// 修饰符
int classMods = cl.getModifiers() &
(Modifier.PUBLIC | Modifier.FINAL |
Modifier.INTERFACE | Modifier.ABSTRACT);
// 办法
Method[] methods = cl.getDeclaredMethods();
if ((classMods & Modifier.INTERFACE) != 0) {classMods = (methods.length > 0) ?
(classMods | Modifier.ABSTRACT) :
(classMods & ~Modifier.ABSTRACT);
}
dout.writeInt(classMods);
if (!cl.isArray()) {
// 继承的接口
Class<?>[] interfaces = cl.getInterfaces();
String[] ifaceNames = new String[interfaces.length];
for (int i = 0; i < interfaces.length; i++) {ifaceNames[i] = interfaces[i].getName();}
// 接口名
Arrays.sort(ifaceNames);
for (int i = 0; i < ifaceNames.length; i++) {dout.writeUTF(ifaceNames[i]);
}
}
// 属性
Field[] fields = cl.getDeclaredFields();
MemberSignature[] fieldSigs = new MemberSignature[fields.length];
for (int i = 0; i < fields.length; i++) {fieldSigs[i] = new MemberSignature(fields[i]);
}
Arrays.sort(fieldSigs, new Comparator<MemberSignature>() {public int compare(MemberSignature ms1, MemberSignature ms2) {return ms1.name.compareTo(ms2.name);
}
});
for (int i = 0; i < fieldSigs.length; i++) {
// 成员签名
MemberSignature sig = fieldSigs[i];
int mods = sig.member.getModifiers() &
(Modifier.PUBLIC | Modifier.PRIVATE | Modifier.PROTECTED |
Modifier.STATIC | Modifier.FINAL | Modifier.VOLATILE |
Modifier.TRANSIENT);
if (((mods & Modifier.PRIVATE) == 0) ||
((mods & (Modifier.STATIC | Modifier.TRANSIENT)) == 0))
{dout.writeUTF(sig.name);
dout.writeInt(mods);
dout.writeUTF(sig.signature);
}
}
// 是否有动态初始化
if (hasStaticInitializer(cl)) {dout.writeUTF("<clinit>");
dout.writeInt(Modifier.STATIC);
dout.writeUTF("()V");
}
// 结构器
Constructor<?>[] cons = cl.getDeclaredConstructors();
MemberSignature[] consSigs = new MemberSignature[cons.length];
for (int i = 0; i < cons.length; i++) {consSigs[i] = new MemberSignature(cons[i]);
}
Arrays.sort(consSigs, new Comparator<MemberSignature>() {public int compare(MemberSignature ms1, MemberSignature ms2) {return ms1.signature.compareTo(ms2.signature);
}
});
for (int i = 0; i < consSigs.length; i++) {MemberSignature sig = consSigs[i];
int mods = sig.member.getModifiers() &
(Modifier.PUBLIC | Modifier.PRIVATE | Modifier.PROTECTED |
Modifier.STATIC | Modifier.FINAL |
Modifier.SYNCHRONIZED | Modifier.NATIVE |
Modifier.ABSTRACT | Modifier.STRICT);
if ((mods & Modifier.PRIVATE) == 0) {dout.writeUTF("<init>");
dout.writeInt(mods);
dout.writeUTF(sig.signature.replace('/', '.'));
}
}
MemberSignature[] methSigs = new MemberSignature[methods.length];
for (int i = 0; i < methods.length; i++) {methSigs[i] = new MemberSignature(methods[i]);
}
Arrays.sort(methSigs, new Comparator<MemberSignature>() {public int compare(MemberSignature ms1, MemberSignature ms2) {int comp = ms1.name.compareTo(ms2.name);
if (comp == 0) {comp = ms1.signature.compareTo(ms2.signature);
}
return comp;
}
});
for (int i = 0; i < methSigs.length; i++) {MemberSignature sig = methSigs[i];
int mods = sig.member.getModifiers() &
(Modifier.PUBLIC | Modifier.PRIVATE | Modifier.PROTECTED |
Modifier.STATIC | Modifier.FINAL |
Modifier.SYNCHRONIZED | Modifier.NATIVE |
Modifier.ABSTRACT | Modifier.STRICT);
if ((mods & Modifier.PRIVATE) == 0) {dout.writeUTF(sig.name);
dout.writeInt(mods);
dout.writeUTF(sig.signature.replace('/', '.'));
}
}
dout.flush();
MessageDigest md = MessageDigest.getInstance("SHA");
byte[] hashBytes = md.digest(bout.toByteArray());
long hash = 0;
for (int i = Math.min(hashBytes.length, 8) - 1; i >= 0; i--) {hash = (hash << 8) | (hashBytes[i] & 0xFF);
}
return hash;
} catch (IOException ex) {throw new InternalError(ex);
} catch (NoSuchAlgorithmException ex) {throw new SecurityException(ex.getMessage());
}
}
从下面这段源码大抵来看,其实这个计算 `serialVersionUID
, 根本是将类名,属性名,属性修饰符,继承的接口,属性类型,名称,办法,动态代码块等等 … 这些都思考进去了,都写到一个DataOutputStream
中,而后再做hash 运算
,所以说, 这个货色得指定啊,不指定的话,略微一改类的货色,就变了 …
而且这个货色指定了,没啥事,不要改!!!除非你确定两个版本就不兼容!!!
没事 …
没事..
没事.
没事
没
此文章仅代表本人(本菜鸟)学习积攒记录,或者学习笔记,如有侵权,请分割作者删除。人无完人,文章也一样,文笔稚嫩,在下不才,勿喷,如果有谬误之处,还望指出,感激不尽~
技术之路不在一时,山高水长,纵使迟缓,驰而不息。
公众号:秦怀杂货店