一、作用
序列化是指把一个 Java 对象变成二进制内容(字节序列),本质上就是一个
byte[]
数组。将数据从程序 (内存) 中写到磁盘、光盘等存储设备中,输出流。
为什么要把 Java 对象序列化呢?因为序列化后可以把
byte[]
保存到文件中,或者把byte[]
通过网络传输到远程,这样,就相当于把 Java 对象存储到文件或者通过网络传输出去了。反序列化,即把二进制内容(也就是
byte[]
数组)恢复为原先的 Java 对象。读取外部数据(磁盘、光盘等存储设备的数据)到程序(内存)中,输入流
有了反序列化,保存到文件中的
byte[]
数组又可以 “变回” Java 对象,或者从网络上读取byte[]
并把它 “变回” Java 对象。
二、实现:使用ObjectInputStream 和 ObjectOutputStream
1、序列化
把一个 Java 对象变为 byte[]
数组,需要使用 ObjectOutputStream
。它负责把一个 Java 对象写入一个字节流:
ObjectOutputStream
(对象输出流)既可以写入基本类型,如 int
,boolean
,也可以写入 String
(以 UTF-8 编码),还可以写入实现了 Serializable
接口的 Object
。
1 | public class Student implements Serializable { |
1 | public static void serialize() throws IOException { |
2、反序列化
ObjectInputStream
负责从一个字节流读取 Java 对象:
1 | public static void deserialize() throws IOException, ClassNotFoundException { |
readObject()
可能抛出的异常有:
ClassNotFoundException
:没有找到对应的 Class;
InvalidClassException
:Class 不匹配。
对于
ClassNotFoundException
,这种情况常见于一台电脑上的 Java 程序把一个 Java 对象,例如,Person
对象序列化以后,通过网络传给另一台电脑上的另一个 Java 程序,但是这台电脑的 Java 程序并没有定义Person
类,所以无法反序列化。
对于
InvalidClassException
,为IOException
的子类。这种情况常见于序列化的Person
对象定义了一个int
类型的age
字段,但是反序列化时,Person
类定义的age
字段被改成了long
类型。所以导致 class 不兼容。为了避免这种 class 定义变动导致的不兼容,可以自定义一个serialVersionUID
。
三、Serializable
接口和serialVersionUID
静态常量
1、Serializable
接口
查看ObjectOutputStream
的writeObject(Object obj)
方法,在源码writeObject0()
下:
如果一个对象既不是字符串、数组、枚举,也没有实现Serializable
接口,序列化时会抛出异常。
一个 Java 对象要能序列化,必须实现一个特殊的 java.io.Serializable
接口,它的定义如下:
1 | public interface Serializable { |
Serializable
接口没有定义任何方法,它是一个空接口。即标记接口(Marker Interface)。
2、serialVersionUID
静态常量
Java 的序列化允许 class 定义一个特殊的 serialVersionUID
静态变量,用于标识 Java 类的序列化 “版本”,通常可以由 IDE 自动生成。serialversionUID
不一致会抛出序列化运行时异常。
1 | private static final long serialVersionUID = -4392658638228508589L; |
serialVersionUID 是序列化前后的唯一标识符,其实是验证版本一致性的。
在反序列化时,JVM 会把字节流中的序列号 ID 和被序列化类中的序列号 ID 做比对,如果不一致,会报
InvalidClassException
异常。一旦类实现了
Serializable
,建议明确的定义一个serialVersionUID
。不然在修改类后,反序列化会发生异常。因为如果没有显式定义
serialVersionUID
,系统会默认定义一个。在类发生改变后,系统定义的serialVersionUID
会发生变化,则反序列化时发现版本不一致,就会发生异常。
- alibaba手册中要求谨慎修改
serialversionUID
。
1θ.【强制】序列化类新增属性时,请不要修改 serialversionUID 字段,避免反序列失败:如果完全不兼容升级,避免反序列化混乱,那么请修改 serialversionUID 值。
说明:注意 serialversionUID 不一致会抛出序列化运行时异常
- 两种显式生成方式:
private static final long serialVersionUID = 1L;
- 或者借助 IDE 自动生成
private static final long serialVersionUID = xxxxL;
。
四、static
修饰的字段 或 transient
修饰的字段不会被序列化
1、被 static
修饰的字段,不会被序列化
序列化保存的是对象的状态而非类的状态,序列化并不保存静态变量。
2、被 transient
修饰符修饰的字段不会被序列化
在变量声明前加上transient
关键字,可以阻止该变量被序列化到文件中,在被反序列化后,transient 变量的值被设为初始值,如 int 型的是 0,对象型的是 null。
五、父类的序列化
1、若父类实现 Serializable
接口,则其子类自动实现Serializable
接口,可以序列化。
序列化该子类对象,子类变量和继承的父类变量都会序列化。
父类实现了 Serializable,子类没有,父类有 Integer a;Integer b ,子类有 Integer c;Integer d。
序列化子类,a,b,c,d都会被序列化。
2、若子类实现 Serializable
接口,而父类没有实现 Serializable
接口。
序列化子类实例的时候,父类的属性是直接被跳过不保存,父类变量值都是无参构造函数后的值。
子类实现了 Serializable,而父类没有,父类有 Integer a ;Integer b ,子类有 Integer c ; Integer d 。
序列化子类,子类变量c,d会被序列化。反序列化后获取到父类变量值 a=null,b=null。
- 如果子类实现
Serializable
接口,父类没有实现,父类需要有默认的无参的构造函数。
父类没有实现 Serializable 接口时,虚拟机是不会序列化父对象的,而一个 Java 对象的构造必须先有父对象,才有子对象,反序列化也不例外。所以反序列化时,为了构造父对象,只能调用父类的无参构造函数作为默认的父对象。因此当我们取父对象的变量值时,它的值是调用父类无参构造函数后的值。
在父类无参构造函数中,若未对变量进行初始化,父类变量值都是默认声明的值,如 int 型的默认是 0,string 型的默认是 null。
如果父类没有无参构造函数,或抛出异常:java.io.InvalidClassException : no valid constructor
。