java序列化和反序列化(二)—— serialVersionUID

上一篇《java序列化和反序列化(一)——概念及Demo分析》中了解到序列化和反序列化的一些基本概念,本篇着重讲一下关于序列化版本UID(即serialVersionUID)的一些问题

1. 一个疑问引发的思考

我们通常在实现 java.io.Serializable 接口时,会在实现类中加一个静态变量,类似下面这样(下面例子中的serialVersionUID是借助IDE自动生成)

private static final long serialVersionUID = -891097293389723583L;

为什么要加上这个变量呢?作用又是什么?下面借助一个例子来说明。

2. 举例

Student.java
public class Student implements Serializable {

        private String name;

        private int age;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public int getAge() {
            return age;
        }

        public void setAge(int age) {
            this.age = age;
        }

    }
SerializableTest.java
public class SerializableTest {

    /**
     * Student序列化
     */
    public static void serialize() {

        Student student = new Student();
        student.setAge(15);
        student.setName("张三");

        ObjectOutputStream ots = null;
        try {
            //指定java对象Person序列化后的字节流输出的目标流
            ots = new ObjectOutputStream(new FileOutputStream("D:/studentStream.txt"));
            //将Person序列化成的字节流写入到目标输出流(此处为文件输出流)中
            ots.writeObject(student);
            System.out.println("序列化成功!");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (ots != null) {
                    ots.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * Student序列化字节的反序列化操作
     */
    public static void deSerialize() {
        ObjectInputStream inputStream = null;
        try {
            //指定序列化字节流的来源
            inputStream = new ObjectInputStream(new FileInputStream("D:/studentStream.txt"));
            //将字节流反序列化成Object对象(在这里进行了强转)
            Student student = (Student) inputStream.readObject();
            System.out.println("执行反序列化过程成功:" + student.getName() + "-" + student.getAge() + "岁");
        } catch (ClassNotFoundException | IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (inputStream != null) {
                    inputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }
    
    public static void main(String[] args) throws InterruptedException {
        serialize();
        //deSerialize();
    }

}

step1:
在SerializableTest.java的main方法中,反序列化方法: deSerialize() 暂时先注掉,第一步我们先仅执行序列化方法:serialize()

public static void main(String[] args) throws InterruptedException {
        serialize();
        //deSerialize();
    }

执行成功后,仍可在"D:/studentStream.txt"路径下找到序列化后的字节流文件
java序列化和反序列化(二)—— serialVersionUID_第1张图片
step2:
在Student.java中的增加num属性

public class Student implements Serializable {

        private String name;

        private int age;

		//新增属性num
        private long num;

        public long getNum() {
            return num;
        }

        public void setNum(long num) {
            this.num = num;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public int getAge() {
            return age;
        }

        public void setAge(int age) {
            this.age = age;
        }

    }

step3:
在SerializableTest.java的main方法中,仅执行反序列化方法:deSerialize()

    public static void main(String[] args) throws InterruptedException{
        //serialize();
        deSerialize();
    }

执行结果如下

java.io.InvalidClassException: io.SerializableTest$Student; 
local class incompatible: 
stream classdesc serialVersionUID = -2732171979826434169, 
local class serialVersionUID = -3422328592789288580
	at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:699)
	at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1885)
	at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1751)
	at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2042)
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1573)
	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:431)
	at io.SerializableTest.deSerialize(SerializableTest.java:88)
	at io.SerializableTest.main(SerializableTest.java:107)

结果分析

  • 失败原因: 从执行结果来看,之所以报错,原因是:从我们step1执行后生成的 字节流文件(studentStream.txt) 反序列化后的Student对象中的serialVersionUID和刚刚 增加了num属性后的Student.java 中运行时生成的serialVersionUID不一致,故导致反序列化失败

  • 结论
    1) Java在运行时,jvm会自动为每个需要序列化且未指定serialVersionUID的实例化对象自动生成一个serialVersionUID

    2)java类如未显示指定serialVersionUID变量,则该类内的属性和方法每做一次变更,jvm便每次随机分配一个serialVersionUID。如上述Student类,在未加num属性时,序列化后时生成的serialVersionUID和增加num后生成的不同,所以才导致的反序列化失败。

    3)java做反序列化操作时,是根据序列化版本UID去做对比和判断,对比一致则进入正常序列化操作,不一致则抛 java.io.InvalidClassException 异常

3. 基于上述的过程及结论我们探究指定serialVersionUID的作用

还是上述步骤,只不过这一次,我们在step1操作之前,先在Student.java中加上

private static final long serialVersionUID = 1L;

然后按上述中step1->step2->step3顺序执行

执行结果如下
java序列化和反序列化(二)—— serialVersionUID_第2张图片
我们可以看到,执行成功,也就是说:当我们显示指定serialVersionUID后,Student的序列化版本UID并没有随着属性和方法的变动而改变

结论

  1. Java的反序列化执行,先会对比字节流转换实例化对象后的serialVersionUID和当前类实例化对象中的serialVersionUID,如果一致,则序列化成功,不一致则抛异常
  2. 在需要序列化的实例中(实现了Serializable接口)显示指定serialVersionUID后,不论类中的属性和方法如何变动,在序列化和反序列化时,拿到的都是同一个serialVersionUID(被指定的那个),通过此,以保证同一类的实例化对象反序列化成功

你可能感兴趣的:(java编程)