Java 序列化Serializable
————————————————
版权声明:本文为优快云博主「ratelfu」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.youkuaiyun.com/weter_drop/article/details/84660173
一 、基本概念
1.1 概念
序列化:将对象的状态信息转换为可以存储或传输的形式的过程
反序列化:将字节序列恢复为对象的过程
1.2 基本用途
- 把对象的字节序列永久存储在硬盘上,通常是一个文件
- 在网络上传输对象的字节序列
二、JDK中的序列化API
FileOutputStream fileOutputStream = new FileOutputStream("person.txt");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
objectOutputStream.writeObject(person);
FileInputStream fileInputStream = new FileInputStream("person.txt");
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
person1 = (Person) objectInputStream.readObject();
Java的序列化机制是通过判断类的serialVersionUID来验证版本一致性的
在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体类的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常,即是InvalidCastException。
UID显式的生成方式:
没有显式地定义一个serialVersionUID变量时候,Java序列化机制会根据编译的Class自动生成一个serialVersionUID作序列化版本比较用,这种情况下,如果Class文件(类名,方法明等)没有发生变化(增加空格,换行,增加注释等等),就算再编译多次,serialVersionUID也不会变化的。
代码示例:
普通实体类 Person.java
import java.io.Serializable;
public class Person implements Serializable {//序列化必须继承Serializable接口
private static final long serialVersionUID = 123l;
private int id;
private String name;
public Person(int id, String name) {
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "Person{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
序列化和反序列化 SerialTest.java
import java.io.*;
public class SerialTest {
public static void main(String[] args) throws IOException, ClassNotFoundException {
Person person = new Person(123, "zhan");
System.out.println(person.toString());
FileOutputStream fileOutputStream = new FileOutputStream("person.txt");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
objectOutputStream.writeObject(person);
objectOutputStream.flush();
objectOutputStream.close();
Person person1;
FileInputStream fileInputStream = new FileInputStream("person.txt");
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
person1 = (Person) objectInputStream.readObject();
objectInputStream.close();
System.out.println(person1.toString());
}
}
三、 四种情形
-
假设Person被序列化后,从A传到B,A和B中都有同一个类,但是他们的serialVersionUID不一样。
报错:
Exception in thread "main" java.io.InvalidClassException: com.sf.code.serial.Person; local class incompatible: stream classdesc serialVersionUID = 1234567890, local class serialVersionUID = 123456780
-
假设AB中UID一样,但是A端增加一个字段,然后序列化,B端不变。
结果:正常执行序列化,反序列化正常,但是A端增加的字段丢失(被B端忽略)
-
假设AB中UID一样,如果B端减少一个字段,A端不变。
结果:序列化、反序列化正常,B端字段少于A端,A端多的字段丢失(被B端忽略)
-
假设AB中UID一样,如果B端增加一个字段,A端不变。
结果:序列化、反序列化正常,B端新增字段被赋予默认值。
四、静态变量序列化
静态变量不会被序列化,因为序列化是保存对象的状态,而静态变量是类的状态。
五、父类的序列化
情境:一个子类实现了 Serializable 接口,它的父类都没有实现 Serializable 接口,序列化该子类对象,然后反序列化后输出父类定义的某变量的数值,该变量数值与序列化时的数值不同。
解决:
-
要想将父类对象也序列化,就需要让父类也实现Serializable 接口。
-
如果父类不实现的话的,就 需要有默认的无参的构造函数。在父类没有实现 Serializable 接口时,虚拟机是不会序列化父对象的,而一个 Java 对象的构造必须先有父对象,才有子对象,反序列化也不例外。所以反序列化时,为了构造父对象,只能调用父类的无参构造函数作为默认的父对象。因此当我们取父对象的变量值时,它的值是调用父类无参构造函数后的值。如果你考虑到这种序列化的情况,在父类无参构造函数中对变量进行初始化,否则的话,父类变量值都是默认声明的值,如 int 型的默认是 0,string 型的默认是 null。
六、Transient关键字
作用: 控制变量的序列化。在变量声明加上该关键字,可以阻止该变量被序列化。在反序列之后,transient变量的值被初始为默认值。int->0, 对象->null
不使用transient使得字段不被序列化的方法:将不要被序列化的字段抽取出来放到弗雷中,子类实现serializable接口,父类不实现。