Java序列化

什么是Java序列化?

持久化内存中的对象到硬件设备,会把其状态保存为一组字节,在未来,再将这些字节组装成对象,这就是序列化和反序列化。
必须注意地是,对象序列化保存的是对象的"状态",即它的成员变量。由此可知,对象序列化不会关注类中的静态变量以及被transient关键字修饰的成员变量。

Java序列化的应用场景

  • 把对象持久化到存储设备上
  • 对象通过网络传输给其它客户端

基本知识点

Serializable概述

  • 对于任何需要被序列化的对象,都必须要实现接口Serializable,它只是一个标识接口,本身没有任何成员,只是用来标识说明当前的实现类的对象可以被序列化.
  • 如果父类实现序列化,子类自动实现序列化,不需要显式实现Serializable接口。

transient关键字

如果某实例变量不能或不应该被序列化,就把它标记为transient的变量,这样序列化程序就会把它跳过。
transient的引用变量会以null返回,基本数据类型会以相应的默认值返回。
(例如:引用类型没有实现Serializable,或者动态数据只可以在执行时求出而不能或不必存储)

serialVersionUID

作用:
用来辅助序列化和反序列化过程的,原则上序列化后的数据中的serialVersionUID只有和当前类的serialVersionUID相同才能够正常地被反序列化。

工作机制:
序列化的时候系统会把当前类的serialVersionUID写入序列化的文件中,当反序列化的时候系统会去检测文件中的serialVersionUID,看它是否和当前类的serialVersionUID一致,如果一致就说明序列化的类的版本和当前类的版本是相同的,这个时候可以成功反序列化;否则就说明当前类和序列化的类相比发生了某些变换,比如成员变量的数量,类型可能发生了改变,这个时候无法正常反序列化的。并会产生以下异常:

java.io.InvalidClassException: 实体类(pojo); local class incompatible: stream classdesc serialVersionUID = 812952289507407815, local class serialVersionUID = -7688346538714640295 

两种指定serialVersionUID的方式:

  • 手动指定serialVersionUID的值,比如100L
  • 通过IDE根据当前类的结构自动去生成它的hash值。

指定与不指定serialVersionUID有什么不同的结果?

  • 如果不手动指定serialVersionUID的值,反序列化时当前类有所改变,比如增加或者删除了某些成员变量,那么系统就会重新计算当前类的hash值并把它赋值给serialVersionUID,这个时候当前类的serialVersionUID就和序列化的数据中的serialVersionUID不一致,于是反序列化失败,程序就会抛出java.io.InvalidClassException。
  • 如果手动指定了它以后,就可以在很大的程度上避免反序列化过程的失败。比如当版本升级后,我们可能删除了某个成员变量也可能增加了一些新的成员变量,这个时候我们的反序列化过程仍然能成功,程序仍然能够最大限度地恢复数据,相反,如果不指定serialVersionUID的话,程序则会crash。
  • 如果类结构发生了非常规性改变,比如修改了类名,修改了成员变量的类型,这个时候尽管serialVersionUID验证通过,但是反序列化过程还是会失败,因为类的结构有了毁灭性的改变,根本无法从老版本的数据中还原出一个新的类结构的对象。

简单实例

        File file = new File("box.out");

        FileOutputStream fileOutputStream = new FileOutputStream(file);
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
        objectOutputStream.writeObject(new Box(100, 100));
        objectOutputStream.close();

        FileInputStream fileInputStream = new FileInputStream(file);
        ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
        Box o = (Box) objectInputStream.readObject();
        objectInputStream.close();
        System.out.println(o);

注意事项

  • 如果有不能被序列化的对象,执行期间就会抛出NotSerializableException异常;
  • 序列化时,只对对象的状态进行保存,而不管对象的方法
  • 静态变量和transient修饰的变量不会被序列化
  • 如果子类实现Serializable接口而父类未实现时,父类不会被序列化,但此时父类必须有个无参构造方法,否则会抛InvalidClassException异常。因为反序列化时会恢复原有子对象的状态,而父类的成员变量也是原有子对象的一部分。由于父类没有实现序列化接口,即使没有显示调用,也会默认执行父类的无参构造函数使变量初始化。

你可能感兴趣的:(Java序列化)