Java--序列化对象

what?

Java序列化是指把Java对象转换为二进制字节码并持久化到磁盘上的过程,Java反序列化是指把二进制码重新从磁盘读取并转换成Java对象的过程。

why?

两种特定情况下需要使用序列化和反序列化:

  • 如果某个对象需要在程序终止后保存,并在程序重新启动后再次使用,就需要在程序终止前将该对象序列化为二进制字节码并持久化到磁盘上,当程序再次启动后从磁盘读取字节码并转换为Java对象。
  • 对象进行网络传输时需要序列化和反序列化。因为在数据只能以二进制的形式在网络中传输。发送方将对象序列化后发出,接收方接收数据后反序列化为Java对象。

How?

最基础的实现:一个对象如果需要序列化,则相应的Class必须直接或者间接实现java.io.Serializable接口。也就是说它和它的某个父类实现有该接口即可。

注意1:Object没有实现Serializable接口,也就是说默认自定义的对象不支持序列化,但String、数组等实现有Serializable接口。

注意2:该类所有无法序列化的字段必须使用transient修饰。这种字段包括两种: 一种是主观上不想保存的属性, 如动态生成的属性或者考虑到性能上的要求不准备保存的属性; 另一种是由于该属性的类型没有实现序列化而无法保存的属性, 如Thread类型的属性。

Operate:

序列化一个对象使用ObjectOutputStream类的writeObject(obj)方法,反序列化一个对象使用ObjectInputStream类readObject()方法。

注意:由于ObjectInputStream.readObject()方法可以反序列化任何类的对象, 所以其返回类型为Object, 我们需要将其强转成具体的类。

举个栗子:

class Student implements Serializable{
      private String name;
      public Student(String name){ this.name = name; }
      public String getName(){ return name; }
   }

class StudentSerializer{
    public static void main(String[] args) throws Exception{
    //创建对象
    Student st = new Student("testSerializable");
    //序列化对象到test.txt
    ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test.txt"));
    oos.writeObject(st);
    oos.close();
    //读取对象,反序列化
    ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test.txt"));
    Student test_st = (Student) ois.readObject();    //注意这里需要强转
    ois.close();
    //验证
    assert "testSerializable".equals(test_st.getName());
    }
}

同时我们也可以想到,如果想要在序列化和反序列化时做一些事情,比如对一些字段的加解密,只需要简单的重写writeObject(obj)和readObject(obj)方法即可。

More: 

序列化ID问题(serialVersionUID)

serialVersionUID 用来表明类的不同版本间的兼容性。有两种生成方式: 一个是默认的1L;一种是随机生成一个不重复的 long 类型数据。

虚拟机是否允许反序列化,不仅取决于类路径和功能代码是否一致,还取决于是两个类的序列化 ID 是否一致。

如果不指定serialVersionUID,Java自动生成。但这会引出一个问题:如果将一个对象序列化到磁盘上,这时候改动了类,在去读这个对象文件的时候就会报错InvalidClassException。所以建议指定serialVersionUID。

静态变量序列化问题

类中静态变量是不参加序列化的。因为序列化保存的是对象的状态,而静态变量属于类的状态。

也就是说当我们序列化一个对象到磁盘,然后改变了静态变量,那么反序列化该对象后它的静态变量的值是更新后的值。

继承中的序列化问题

子类实现 Serializable 接口而父类没有实现,那么父类不会被序列化,而且父类必须有无参构造函数。

父类如果没有实现 Serializable 接口,虚拟机不会序列化父对象。而一个 Java 对象的构造必须先有父对象,才有子对象,反序列化也不例外。所以反序列化时,为了构造父对象,只能调用父类的无参构造函数作为默认的父对象。所以在编程时这里可能会有一个坑:如果父类没有实现Serializable 接口,我们反序列化一个子类的对象,发现它的父类属性值都变成了默认值。

序列化存储规则

如果我们连续序列化同一个对象到相同文件,那么JVM不会存储两次对象的内容,而是第二次会存储一个引用。反序列化时会恢复引用关系。也就是说我们反序列化这两个对象后,发现两个引用指向同一个对象。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

编辑于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券