JAVA序列化

158 阅读5分钟

转载自:mp.weixin.qq.com/s?__biz=MzU…

序列化作用:

  1. 序列化:把Java对象转换为字节序列。
  2. 反序列化:把字节序列恢复为原先的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号作用

private static final long serialVersionUID = -4392658638228508589L;

我们首先还是调用上面的serialize()方法,将一个Student对象序列化到本地磁盘上的student.txt文件:

接下来我们在Student类里面动点手脚,比如在里面再增加一个名为studentID的字段,表示学生学号:

这时候,我们拿刚才已经序列化到本地的student.txt文件,还用如下代码进行反序列化,试图还原出刚才那个Student对象:

运行发现报错了,并且抛出了InvalidClassException异常:

这地方提示的信息非常明确了:序列化前后的serialVersionUID号码不兼容! 从这地方最起码可以得出两个重要信息:

  • serialVersionUID是序列化前后的唯一标识符
  • 默认如果没有人为显式定义过serialVersionUID,那编译器会为它自动声明一个!

1、 第1个问题: serialVersionUID序列化ID,可以看成是序列化和反序列化过程中的“暗号”,在反序列化时,JVM会把字节流中的序列号ID和被序列化类中的序列号ID做比对,只有两者一致,才能重新反序列化,否则就会报异常来终止反序列化的过程。

2、 第2个问题: 如果在定义一个可序列化的类时,没有人为显式地给它定义一个serialVersionUID的话,则Java运行时环境会根据该类的各方面信息自动地为它生成一个默认的serialVersionUID,一旦像上面一样更改了类的结构或者信息,则类的serialVersionUID也会跟着变化!

所以,为了serialVersionUID的确定性,写代码时还是建议,凡是implements Serializable的类,都最好人为显式地为它声明一个serialVersionUID明确值! 当然,如果不想手动赋值,你也可以借助IDE的自动添加功能,比如我使用的IntelliJ IDEA,按alt + enter就可以为类自动生成和添加serialVersionUID字段,十分方便: