吃透Java基础九:序列化

一:什么是序列化

序列化是将Java对象相关的类信息、属性、属性值等信息以一定的格式转换为字节流,反序列化时再将字节流表示的信息来构建出Java对象。过程中涉及到其它对象的引用对象也要参与序列化。

二:序列化的应用场景

1. 永久性保存对象,保存对象的字节序列到本地文件或者数据库中。

2. 通过序列化以字节流的形式使对象在网络中进行传递和接收。

3. 通过序列化在进程间传递对象。

三:序列化的实现方式

Java中实现序列化的方式有两种:1、实现Serializable接口。2、实现Externalizable接口。

1、实现Serializable接口

吃透Java基础九:序列化

运行输出:name:bobo age:26

2、实现Externalizable接口

用Externalizable接口实现序列化时需要注意两点:

  1. 必须要提供公有的无参构造函数,否则会报InvalidClassException。
  2. 必须要在writeExternal和readExternal中自己去实现序列化过程。
吃透Java基础九:序列化

吃透Java基础九:序列化

运行输出:name:bobo age:26

四:序列化核心点

1、serialVersionUID的作用

在序列化操作时,经常会看到实现了Serializable接口的类会存在一个serialVersionUID属性,并且它是一个固定数值的静态变量。它主要用于验证版本的一致性。每个实现Serializable接口的类都拥有这么一个ID,在序列化的时候会一起被写入流中。在反序列化的时候就会被拿出来跟当前类的serialVersionUID值进行比较,两者相同则说明版本一致,可以反序列化成功,如果不通则反序列化失败。

两种serialVersionUID方式:

1. 自己定义,比如比如:private static final long serialVersionUID = 1234567L。

2. 如果没定义,JDK会帮我们生成,生成规则是利用类名、类修饰符、接口名、字段、静态初始化信息、构造函数信息、方法名、方法修饰符、方法签名等组成的信息,经过SHA算法生成serialVersionUID 值。

2、Transient 关键字作用

Transient 关键字的作用是控制变量的序列化,在变量声明前加上该关键字,可以阻止该变量被序列化到流中,在被反序列化后,transient 变量的值被设为初始值,如 int 型的是 0,对象型的是 null。

吃透Java基础九:序列化

运行输出:name:null age:0

3、静态变量不会被序列化

这个也很好理解,我们序列化是针对对象的,而静态变量是属于类的。下面看一个例子:

吃透Java基础九:序列化

运行输出:age:26

age的值改变了,证明age是没有被序列化的。

4、父类的序列化

如果一个子类实现了Serializable 接口而父类没有实现该接口,则在序列化子类时,子类的属性状态会被写入而父类的属性状态不会被写入。**所以如果想要父类属性状态也一起参与序列化,就要让它也实现Serializable 接口。**

如果父类未实现Serializable 接口则反序列化生成的对象会再次调用父类的构造函数,以此来完成对父类的初始化,所以父类的属性初始值一般都是类型的默认值。

吃透Java基础九:序列化

吃透Java基础九:序列化

运行输出:

吃透Java基础九:序列化

5、被引用的类没有实现Serializable 接口则序列化不成功

序列化对象里面包含的任何引用类型的对象的类都要实现Serializable 接口,否则抛出java.io.NotSerializableException。

吃透Java基础九:序列化

吃透Java基础九:序列化

运行输出:

吃透Java基础九:序列化

6、自定义序列化过程

如果默认的序列化过程不能满足需求,我们也可以自定义整个序列化过程。这时候我们只需要在需要序列化的类中定义私有的writeObject方法和readObject方法即可。

吃透Java基础九:序列化

吃透Java基础九:序列化

运行输出:

吃透Java基础九:序列化

在color属性前加了transient 关键字,意思是不让color实现序列化,但是下面又自定义序列化过程在writeObject和readObject里面实现color的序列化,所以color属性是实现了序列化的。

7、为什么实现readObject()方法和writeObject()方法就可以自定义序列化过程?

readObject()和writeObject() 既不存在于java.lang.Object,也没有在Serializable中声明。那么ObjectOutputStream如何使用它们的呢?原来,**ObjectOutputStream使用了反射来寻找是否声明了这两个方法。因为ObjectOutputStream使用getPrivateMethod,所以这些方法不得不被声明为private以至于供ObjectOutputStream来使用。**

下面我们以ObjectInputStream来源码分析一下:

ObjectInputStream的readObject()方法--->调用到readObject0(boolean unshared)方法--->readOrdinaryObject(boolean unshared)方法--->readSerialData(Object obj, ObjectStreamClass desc)方法---->ObjectStreamClass类的invokeReadObject(Object obj, ObjectInputStream in)方法:

吃透Java基础九:序列化

执行readObjectMethod.invoke(obj, new Object[]{ in }),通过反射的方式调用我们类中定义的readObject的私有方法。


分享到:


相關文章: