无聊小知识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!!!

相关文章
|
6月前
|
存储 JSON Java
为什么不能轻易修改 serialVersionUID 字段
为什么不能轻易修改 serialVersionUID 字段
55 0
|
1月前
|
Java
Java关键字 —— static 与 final 详细解释!一看就懂 有代码实例运行!
这篇文章详细解释了Java中static和final关键字的用法,包括它们修饰类、方法、变量和代码块时的行为,并通过代码示例展示了它们的具体应用。
160 0
Java关键字 —— static 与 final 详细解释!一看就懂 有代码实例运行!
|
3月前
|
设计模式
八股day06_static静态修饰符
八股day06_static静态修饰符
|
安全
synchronized工作过程中,具体讨论下synchronized里面都干了啥??
synchronized工作过程中,具体讨论下synchronized里面都干了啥??
38 0
|
Java 编译器
serialVersionUID 不是被 static 变量修饰了吗?为什么还会被“序列化”?
serialVersionUID 不是被 static 变量修饰了吗?为什么还会被“序列化”?
|
安全 Java
|
存储 Java 编译器
【叁】了解对象与类(面向对象思想、final、static静态域和方法)
【叁】了解对象与类(面向对象思想、final、static静态域和方法)
94 0
|
Java Unix 编译器
看完这篇 final、finally 和 finalize 和面试官扯皮就没问题了(二)
final 是 Java 中的关键字,它也是 Java 中很重要的一个关键字,final 修饰的类、方法、变量有不同的含义;finally 也是一个关键字,不过我们可以使用 finally 和其他关键字结合做一些组合操作;finalize 是一个不让人待见的方法,它是对象祖宗 Object 中的一个方法,finalize 机制现在已经不推荐使用了。本篇文章,cxuan 就带你从这三个关键字入手,带你从用法、应用、原理的角度带你深入浅出理解这三个关键字。
159 0
看完这篇 final、finally 和 finalize 和面试官扯皮就没问题了(二)
|
Java 编译器 程序员
看完这篇 final、finally 和 finalize 和面试官扯皮就没问题了(一)
final 是 Java 中的关键字,它也是 Java 中很重要的一个关键字,final 修饰的类、方法、变量有不同的含义;finally 也是一个关键字,不过我们可以使用 finally 和其他关键字结合做一些组合操作;finalize 是一个不让人待见的方法,它是对象祖宗 Object 中的一个方法,finalize 机制现在已经不推荐使用了。本篇文章,cxuan 就带你从这三个关键字入手,带你从用法、应用、原理的角度带你深入浅出理解这三个关键字。
108 0
看完这篇 final、finally 和 finalize 和面试官扯皮就没问题了(一)
|
测试技术 C++
软件测试面试题:在C/C++中static有什么用途?(请至少说明两种)
软件测试面试题:在C/C++中static有什么用途?(请至少说明两种)
122 0