前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >serivalVersionUID是干嘛用的?

serivalVersionUID是干嘛用的?

原创
作者头像
秦怀杂货店
修改2020-11-23 11:36:48
1.9K0
修改2020-11-23 11:36:48
举报
文章被收录于专栏:技术杂货店技术杂货店

正常不设置serialVersionUID 的序列化和反序列化

先定义一个实体Student.class,需要实现Serializable接口,但是不需要实现get(),set()方法

代码语言:txt
复制
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文件反序列化成对象,输出。

代码语言: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()方法。

代码语言:txt
复制
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()方法,会报错:

代码语言:txt
复制
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/article/details/80620804

代码语言:txt
复制
 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生成实际上也是调用这个命令,代码调用可以这样写:

代码语言:txt
复制
ObjectStreamClass c = ObjectStreamClass.lookup(Student.class);
long serialID = c.getSerialVersionUID();
System.out.println(serialID);

如果不显示的指定,那么不同JVM之间的移植可能也会出错,因为不同的编译器,计算这个值的策略可能不同,计算类没有修改,也会出现不一致的问题。

getSerialVersionUID()源码如下:

代码语言:txt
复制
 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():

代码语言:txt
复制
 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运算,所以说,这个东西得指定啊,不指定的话,稍微一改类的东西,就变了...

而且这个东西指定了,没啥事,不要改!!!除非你确定两个版本就不兼容!!!

【作者简介】

秦怀,公众号【秦怀杂货店】作者,技术之路不在一时,山高水长,纵使缓慢,驰而不息。这个世界希望一切都很快,更快,但是我希望自己能走好每一步,写好每一篇文章,期待和你们一起交流。

此文章仅代表自己(本菜鸟)学习积累记录,或者学习笔记,如有侵权,请联系作者核实删除。人无完人,文章也一样,文笔稚嫩,在下不才,勿喷,如果有错误之处,还望指出,感激不尽~

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 正常不设置serialVersionUID 的序列化和反序列化
  • 序列化之后,类文件增加了字段,反序列化会怎么样?
  • serialVersionUID生成以及作用?
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档