失常不设置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运算,所以说,这个货色得指定啊,不指定的话,略微一改类的货色,就变了...

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

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

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

公众号:秦怀杂货店