失常不设置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运算
,所以说,这个货色得指定啊,不指定的话,略微一改类的货色,就变了…
而且这个货色指定了,没啥事,不要改!!!除非你确定两个版本就不兼容!!!
没事…
没事..
没事.
没事
没
此文章仅代表本人(本菜鸟)学习积攒记录,或者学习笔记,如有侵权,请分割作者删除。人无完人,文章也一样,文笔稚嫩,在下不才,勿喷,如果有谬误之处,还望指出,感激不尽~
技术之路不在一时,山高水长,纵使迟缓,驰而不息。
公众号:秦怀杂货店
发表回复