Java序列化是指把Java对象转换为二进制字节码并持久化到磁盘上的过程,Java反序列化是指把二进制码重新从磁盘读取并转换成Java对象的过程。
两种特定情况下需要使用序列化和反序列化:
最基础的实现:一个对象如果需要序列化,则相应的Class必须直接或者间接实现java.io.Serializable接口。也就是说它和它的某个父类实现有该接口即可。
注意1:Object没有实现Serializable接口,也就是说默认自定义的对象不支持序列化,但String、数组等实现有Serializable接口。
注意2:该类所有无法序列化的字段必须使用transient修饰。这种字段包括两种: 一种是主观上不想保存的属性, 如动态生成的属性或者考虑到性能上的要求不准备保存的属性; 另一种是由于该属性的类型没有实现序列化而无法保存的属性, 如Thread类型的属性。
序列化一个对象使用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)方法即可。
serialVersionUID 用来表明类的不同版本间的兼容性。有两种生成方式: 一个是默认的1L;一种是随机生成一个不重复的 long 类型数据。
虚拟机是否允许反序列化,不仅取决于类路径和功能代码是否一致,还取决于是两个类的序列化 ID 是否一致。
如果不指定serialVersionUID,Java自动生成。但这会引出一个问题:如果将一个对象序列化到磁盘上,这时候改动了类,在去读这个对象文件的时候就会报错InvalidClassException。所以建议指定serialVersionUID。
类中静态变量是不参加序列化的。因为序列化保存的是对象的状态,而静态变量属于类的状态。
也就是说当我们序列化一个对象到磁盘,然后改变了静态变量,那么反序列化该对象后它的静态变量的值是更新后的值。
子类实现 Serializable 接口而父类没有实现,那么父类不会被序列化,而且父类必须有无参构造函数。
父类如果没有实现 Serializable 接口,虚拟机不会序列化父对象。而一个 Java 对象的构造必须先有父对象,才有子对象,反序列化也不例外。所以反序列化时,为了构造父对象,只能调用父类的无参构造函数作为默认的父对象。所以在编程时这里可能会有一个坑:如果父类没有实现Serializable 接口,我们反序列化一个子类的对象,发现它的父类属性值都变成了默认值。
如果我们连续序列化同一个对象到相同文件,那么JVM不会存储两次对象的内容,而是第二次会存储一个引用。反序列化时会恢复引用关系。也就是说我们反序列化这两个对象后,发现两个引用指向同一个对象。