无聊小知识01.serialVersionUID的作用

本文涉及的产品
系统运维管理,不限时长
简介: java编码过程中,经常需要对象序列化和反序列化。对象默认要实现Serializable,同时声明serialVersionUID。那么一定要声明serialVersionUID吗?为什么?

什么是serialVersionUID

Java(TM)对象序列化规范中描述到:serialVersionUID用作Serializable类中的版本控件。如果您没有显式声明serialVersionUID,JVM将根据您的Serializable类的各个方面自动为您执行此操作。(http://docs.oracle.com/javase/1.5.0/docs/api/java/io/Serializable.html)

对象序列化

声明对象:

package com.shamee.demo;
public class Student {
    private String name;
    private int age;
    public Student() {
    }
    public Student(String name, int age) {
        this.name = name;
        this.age = 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;
    }
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

创建序列化测试类:

package com.shamee.demo;
import org.junit.jupiter.api.Test;
import java.io.*;
public class ObjectIOStreamTest {
    @Test
    public void writeToStream(){
        try(ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("student.obj")))) {
            oos.writeObject(new Student("张三", 18));
            oos.flush();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    @Test
    public void readForStream(){
        try(ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("student.obj")))) {
            Object o = ois.readObject();
            System.out.println(o.toString());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

运行结果:

网络异常,图片无法展示
|

序列化条件

  1. 实现Serializable接口
  2. 声明serialVersionUID常量。
package com.shamee.demo;
import java.io.Serializable;
public class Student implements Serializable {
    private static final long serialVersionUID = 42L;
    private String name;
    private int age;
    public Student() {
    }
    public Student(String name, int age) {
        this.name = name;
        this.age = 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;
    }
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

在此运行结果:

网络异常,图片无法展示
|

已能够正常序列化。

Serializable接口

查看Serializable接口源码可以看出,该接口没有声明任何方法,是一个标识接口。实现该接口的对象均会被识别为可序列化对象。

/**
The serialization runtime associates with each serializable class a version
 * number, called a serialVersionUID, which is used during deserialization to
 * verify that the sender and receiver of a serialized object have loaded
 * classes for that object that are compatible with respect to serialization.
 * If the receiver has loaded a class for the object that has a different
 * serialVersionUID than that of the corresponding sender's class, then
 * deserialization will result in an {@link InvalidClassException}.  A
 * serializable class can declare its own serialVersionUID explicitly by
 * declaring a field named <code>"serialVersionUID"</code> that must be static,
 * final, and of type <code>long</code>:
 *
 * <PRE>
 * ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L; 
If a serializable class does not explicitly declare a serialVersionUID, then
* the serialization runtime will calculate a default serialVersionUID value
* for that class based on various aspects of the class, as described in the
* Java(TM) Object Serialization Specification.   However, it is strongly
* recommended that all serializable classes explicitly declare
* serialVersionUID values, since the default serialVersionUID computation is
* highly sensitive to class details that may vary depending on compiler
* implementations, and can thus result in unexpected
* InvalidClassExceptions during deserialization.
*/

从注释中可以看出,实现该接口。需要显示声明一个serialVersionUID。用于序列化运行时与每个可序列化类关联一个版本编号,称为serialVersionUID,在反序列化过程中使用验证序列化对象的发送方和接收方是否已经加载。

如果可序列化类没有显式声明serialVersionUID,则序列化运行时将计算一个默认serialVersionUID,因为默认的serialVersionUID计算为高度敏感的类细节,可能变化取决于编译器实现,从而导致意外在反序列化期间InvalidClassException。

简单的说,就是该serialVersionUID用于标识对象序列和反序列化过程中是唯一匹配的。

自动生成的serialVersionUID问题

自动生成的serialVersionUID可能会导致序列化和反序列化中导致异常。具体实验步骤:

  1. 声明student类,实现Serializable接口,不显式声明serialVersionUID
  2. 将对象进行序列化
  3. 修改student类
  4. 将student对象进行反序列化

声明student类:

package com.shamee.demo;
import java.io.Serializable;
public class Student implements Serializable {
    private String name;
    private int age;
    public Student() {
    }
    public Student(String name, int age) {
        this.name = name;
        this.age = 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;
    }
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

序列化:

@Test
    public void writeToStream(){
        try(ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("student.obj")))) {
            oos.writeObject(new Student("张三", 18));
            oos.flush();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

序列化成功:

网络异常,图片无法展示
|

修改student类:

package com.shamee.demo;
import java.io.Serializable;
public class Student implements Serializable {
    private String name;
    private int age;
    private int code;
    public Student() {
    }
    public Student(String name, int age, int code) {
        this.name = name;
        this.age = age;
        this.code = code;
    }
    public int getCode() {
        return code;
    }
    public void setCode(int code) {
        this.code = code;
    }
    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;
    }
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", code=" + code +
                '}';
    }
}

接着反序列化:

网络异常,图片无法展示
|

直接报错,从报错信息可以看出:serialVersionUID自动生成取决于class类的字节码。因为前后类的变更导致对象的serialVersionUID发生变化,导致对象在序列化和反序列化中找不到唯一匹配标识,从而导致异常。

所以需要显式声明serialVersionUID。

如何生成serialVersionUID

IDEA点击File->Editor->Inspections。搜索框搜索UID,选择Serializable class without 'serialVersionUID'右侧复选框打勾,右下角Severity选择warning,点击OK。

网络异常,图片无法展示
|

网络异常,图片无法展示
|

之后序列化类右侧会有警告标签,点击警告标签有提示Add 'serialVersionUID' field,点击即可快速生成serialVersionUID。

无聊的小知识+1!!!

相关文章
|
存储 算法 Java
【面试题精讲】serialVersionUID 有什么作用?
【面试题精讲】serialVersionUID 有什么作用?
|
6月前
|
设计模式
八股day06_static静态修饰符
八股day06_static静态修饰符
|
编译器 C++
C++ 类和对象(静态的static、友元、内部类、匿名对象、explicit)知识点+完整思维导图+实操图+深入细节通俗易懂建议收藏(一)
C++ 类和对象(静态的static、友元、内部类、匿名对象、explicit)知识点+完整思维导图+实操图+深入细节通俗易懂建议收藏(一)
|
编译器 C++
C++ 类和对象(静态的static、友元、内部类、匿名对象、explicit)知识点+完整思维导图+实操图+深入细节通俗易懂建议收藏(二)
C++ 类和对象(静态的static、友元、内部类、匿名对象、explicit)知识点+完整思维导图+实操图+深入细节通俗易懂建议收藏(二)
|
Java
关于Serializable的一个形象的例子
关于Serializable的一个形象的例子
100 0
final关键字:我偏不让你继承
final关键字:我偏不让你继承
140 0
|
Java 编译器 C语言
【重学C/C++系列(八)】:如何理解C++中的void*?
首先void*中的void代表一个任意的数据类型,"星号"代表一个指针,所以其就是一个任意数据类型的指针。
【重学C/C++系列(八)】:如何理解C++中的void*?
|
缓存 Java
JDK源码(10)-Integer(用处最多,重点讲解)
JDK源码(10)-Integer(用处最多,重点讲解)
129 0
|
存储 Java 编译器
【叁】了解对象与类(面向对象思想、final、static静态域和方法)
【叁】了解对象与类(面向对象思想、final、static静态域和方法)
102 0
|
缓存 Java
通过JDK源码角度分析Long类详解
通过JDK源码角度分析Long类详解
通过JDK源码角度分析Long类详解