序列化/反序列化,我忍你很久了

作者:微信小助手

发布时间:2021-10-06T08:08:57

曾几何时,对于Java的序列化的认知一直停留在:「实现个 Serializbale 接口」不就好了的状态,直到 ...
所以这次抽时间再次重新捧起了尘封已久的《Java编程思想》,就像之前梳理《枚举部分知识》一样,把「序列化和反序列化」这块的知识点又重新审视了一遍。

序列化是干啥用的?

序列化的原本意图是希望对一个Java对象作一下“变换”,变成字节序列,这样一来方便持久化存储到磁盘,避免程序运行结束后对象就从内存里消失,另外变换成字节序列也更便于网络运输和传播,所以概念上很好理解:
  • 序列化:把Java对象转换为字节序列。
  • 反序列化:把字节序列恢复为原先的Java对象。
而且序列化机制从某种意义上来说也弥补了平台化的一些差异,毕竟转换后的字节流可以在其他平台上进行反序列化来恢复对象。
事情就是那么个事情,看起来很简单,不过后面的东西还不少,请往下看。

对象如何序列化?
然而Java目前并没有一个关键字可以直接去定义一个所谓的“可持久化”对象。
对象的持久化和反持久化需要靠程序员在代码里手动显式地进行序列化和反序列化还原的动作。
举个例子,假如我们要对 Student 类对象序列化到一个名为 student.txt 的文本文件中,然后再通过文本文件反序列化成 Student 类对象:
1、Student类定义
 
public class Student implements Serializable {

    private String name;
    private Integer age;
    private Integer score;
    
    @Override
    public String toString() {
        return "Student:" + 'n' +
        "name = " + this.name + 'n' +
        "age = " + this.age + 'n' +
        "score = " + this.score + 'n'
        ;
    }
    
    // ... 其他省略 ...
}
2、序列化
 
public static void serialize(  ) throws IOException {

    Student student = new Student();
    student.setName("CodeSheep");
    student.setAge( 18 );
    student.setScore( 1000 );

    ObjectOutputStream objectOutputStream = 
        new ObjectOutputStream( new FileOutputStream( new File("student.txt") ) );
    objectOutputStream.writeObject( student );
    objectOutputStream.close();
    
    System.out.println("序列化成功!已经生成student.txt文件");
    System.out.println("==============================================");
}
3、反序列化
 
public static void deserialize(  ) throws IOException, ClassNotFoundException {
    ObjectInputStream objectInputStream = 
        new ObjectInputStream( new FileInputStream( new File("student.txt") ) );
    Student student = (Student) objectInputStream.readObject();
    objectInputStream.close();
    
    System.out.println("反序列化结果为:");
    System.out.println( student );
}
4、运行结果
控制台打印:
 
序列化成功!已经生成student.txt文件
==============================================
反序列化结果为:
Student:
name = CodeSheep
age = 18
score = 1000

Serializable接口有何用?
上面在定义 Student 类时,实现了一个 Serializable 接口,然而当我们点进 Serializable 接口内部查看,发现它竟然是一个空接口,并没有包含任何方法!
试想,如果上面在定义 Student 类时忘了加 implements Serializable 时会发生什么呢?
实验结果是:此时的程序运行会报错,并抛出 NotSerializableException 异常:
我们按照错误提示,由源码一直跟到 ObjectOutputStream writeObject0() 方法底层一看,才恍然大悟:
如果一个对象既不是字符串数组枚举,而且也没有实现 Serializable 接口的话,在序列化时就会抛出 NotSerializableException 异常!
哦,我明白了!
原来 Serializable 接口也仅仅只是做一个标记用!!!
它告诉代码只要是实现了 Serializable 接口的类都是可以被序列化的!然而真正的序列化动作不需要靠它完成。

serialVersionUID号有何用?

相信你一定经常看到有些类中定义了如下代码行,即定义了一个名为 serialVersionUID 的字段:
 
private static final long serialVersionUID = -4392658638228508589L;
你知道这句声明的含义吗?为什么要搞一个名为serialVersionUID的序列号?
继续来做一个简单实验,还拿上面的 Student 类为例,我们并没有人为在里面显式地声明一个 serialVersionUID 字段。
我们首先还是调用上面的 serialize() 方法,将一个 Student 对象序列化到本地磁盘上的 student.txt 文件:
 
public static void serialize() throws IOException {

    Student student = new Student();
    student.setName("CodeSheep");
    student.setAge( 18 );
    student.setScore( 100 );

    ObjectOutputStream objectOutputStream = 
        new ObjectOutputStream( new FileOutputStream( new File("student.txt") ) );
    objectOutputStream.writeObject( student );
    objectOutputStream.close();
}
接下来我们在 Student 类里面动点手脚,比如在里面再增加一个名为 studentID 的字段,表示学生学号:
这时候,我们拿刚才已经序列化到本地的 student.txt 文件,还用如下代码进行反序列化,试图还原出刚才那个 Student 对象:
 
public static void deserialize(  ) throws IOException, ClassNotFoundException {
    ObjectInputStream objectInputStream = 
        new ObjectInputStream( new FileInputStream( new File("student.txt") ) );
    Student student = (Student) objectInputStream.readObject();
    objectInputStream.close();
    
    System.out.println("反序列化结果为:");
    System.out.println( student );
}