博客
关于我
serialVersionUID作用是什么以及如何生成的?
阅读量:394 次
发布时间:2019-03-05

本文共 10792 字,大约阅读时间需要 35 分钟。

目录

正常不设置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,方法:

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
() { 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
() { 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("
"); 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
() { 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("
"); 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
() { 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运算`,所以说,这个东西得指定啊,不指定的话,稍微一改类的东西,就变了...

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

没事...
没事..
没事.
没事

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

技术之路不在一时,山高水长,纵使缓慢,驰而不息。

公众号:秦怀杂货店

转载地址:http://sbmzz.baihongyu.com/

你可能感兴趣的文章
1062 Talent and Virtue
查看>>
1045 Favorite Color Stripe
查看>>
B. Spreadsheets(进制转换,数学)
查看>>
等和的分隔子集(DP)
查看>>
基础练习 十六进制转八进制(模拟)
查看>>
L - Large Division (大数, 同余)
查看>>
39. Combination Sum
查看>>
41. First Missing Positive
查看>>
80. Remove Duplicates from Sorted Array II
查看>>
83. Remove Duplicates from Sorted List
查看>>
410. Split Array Largest Sum
查看>>
Vue3发布半年我不学,摸鱼爽歪歪,哎~就是玩儿
查看>>
《实战java高并发程序设计》源码整理及读书笔记
查看>>
Java开源博客My-Blog(SpringBoot+Docker)系列文章
查看>>
程序员视角:鹿晗公布恋情是如何把微博搞炸的?
查看>>
Spring+SpringMVC+MyBatis+easyUI整合进阶篇(七)一次线上Mysql数据库崩溃事故的记录
查看>>
【JavaScript】动态原型模式创建对象 ||为何不能用字面量创建原型对象?
查看>>
ClickHouse源码笔记4:FilterBlockInputStream, 探寻where,having的实现
查看>>
Linux应用-线程操作
查看>>
多态体验,和探索爷爷类指针的多态性
查看>>