第 18 章 – java IO系统 – 序列化与反序列化
1. 反序列化时必须在classpath中能找到被序列化的类的class文件.
2. Serializable接口 和 transient关键字
只有实现了Serializable接口的类的实例才能序列化.
这种类型的实例序列化时只存储当前对象的所有字段的二进制值.
不会调用对象的默认构造器,所有对象的默认构造器可以不必是public的.
使用transient关键字修饰的字段,在序列化时不会保存字段的值.
transient关键字修饰的字段在反序列化时出来的值是未初始化的(数字0,或null之类的).
示例代码:
package io; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; class ClsWithTransient implements Serializable { private int i = 0; transient public String s = "初始值"; transient private int iTransient = 111; public void setS(String s) { this.s = s; } public String toString() { return String.format("ClsWithTransient 类: i=%d,transient s=%s,transient iTransient=%d",i,s,iTransient); } } public class TestTransient { public static void main(String[] args) throws Exception { ClsWithTransient cls = new ClsWithTransient(); System.out.println("刚创建的对象 " + cls); String s = "修改的值"; System.out.println("修改对象的字符串值为:" + s); cls.setS(s); System.out.println("修改完成,序列化之对象前 " + cls); ObjectOutputStream obout = new ObjectOutputStream(new FileOutputStream(new File("D:\\D\\transient_obj.out"))); obout.writeObject(cls); obout.flush(); obout.close(); System.out.println("序列化写入完毕!"); System.out.println("\n开始反序列化..."); ObjectInputStream obin = new ObjectInputStream(new FileInputStream(new File("D:\\D\\transient_obj.out"))); ClsWithTransient aCls = (ClsWithTransient) obin.readObject(); obin.close(); System.out.println("反序列化出来的对象为 " + aCls); //反序列化出来的对象为 ClsWithTransient 类: i=0,transient s=null,transient iTransient=0 //表明transient修饰的字段在反序列化时是未初始化的. } /** 输出如下: * 刚创建的对象 ClsWithTransient 类: i=0,transient s=初始值,transient iTransient=111 修改对象的字符串值为:修改的值 修改完成,序列化之对象前 ClsWithTransient 类: i=0,transient s=修改的值,transient iTransient=111 序列化写入完毕! 开始反序列化... 反序列化出来的对象为 ClsWithTransient 类: i=0,transient s=null,transient iTransient=0 */ }
3. Externalizable 接口集成自 Serializable接口
具有两个方法:
void readExternal(ObjectInput in)
对象实现 readExternal 方法来恢复其内容,它通过调用 DataInput
的方法来恢复其基础类型,调用 readObject 来恢复对象、字符串和数组。
void writeExternal(ObjectOutput out)
该对象可实现 writeExternal 方法来保存其内容,
它可以通过调用 DataOutput 的方法来保存其基本值,
或调用 ObjectOutput 的 writeObject 方法来保存对象、
字符串和数组。
这两个方法由系统调用.
实现了Externalizable接口的对象在序列化时,先调用writeExternal(ObjectOutput out) 这个方法.
传递的参数就是 用来写入序列化的ObjectOutputStream 对象,并将该对象传递给
writeExternal方法.ObjectOutputStream对象实现了ObjectOutput接口(是DataOutput的子接口),
该接口有各种类似writeInt等方法,还有writeObject方法可以在序列化时写入想要的保存的信息.
readExternal(ObjectInput in)在反序列化时由系统自动调用.传入当前用来读取序列化文件的
ObjectInputStream来读取序列化时writeExternal(ObjectOutput out)中写入的各种信息,并且
用这些信息来初始化正在反序列化的对象.ObjectInputStream对象实现了ObjectInput接口(继承
自DataInput接口,具有各种类似readInt等方法,还有readObject方法).
在反序列化 Externalizable 对象时的所做的操作如下:
(1) 先初始化字段,注意,transient 修饰的字段也会初始化
(2) 如果有初始化块,则执行初始化块(不调用static修饰的静态初始化块)
(3) 调用无参数的公共构造器(默认构造器)创建一个实例.
因此,序列化的对象,必须要有public的默认构造器(无参构造器).
(4) 调用 readExternal 方法进行初始化:通过从 ObjectInputStream 中读取 Serializable 对象
可以恢复这些对象。
所以,Externalizable序列化时是不会记录该对象的字段的,因为反序列化是依靠对象的默认构造器
和readExternal(ObjectInput in)方法.
比如下面的例子里面,class Blip2 implements Externalizable 没有public的默认构造器
虽然在序列化时可以成功,但是反序列化时报异常:
java.io.InvalidClassException: io.Blip2; io.Blip2; no valid constructor
示例代码:
package io; //: io/Blips.java // Simple use of Externalizable & a pitfall. import java.io.Externalizable; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectInputStream; import java.io.ObjectOutput; import java.io.ObjectOutputStream; class Blip1 implements Externalizable { //反序列化时会依次初始化下面的字段 private int i = 111; private String s ="初始的值"; transient private String sTr ="transient初始的值"; //实现Externalizable的对象中的transient字段依然在反序列化时初始化 private static int iStatic = 222; static //反序列化时不调用static修饰的静态初始化块 { System.out.println("静态 初始化块中的初始化"); iStatic = 333; } //反序列化时,在初始化完字段后,调用初始化快 { System.out.println("初始化块中的初始化"); } public Blip1() { System.out.println("Blip1 Constructor"); } public void writeExternal(ObjectOutput out) throws IOException { System.out.println("Blip1.writeExternal"); out.writeInt(123); out.writeObject("s在writeExternal中保存的值"); out.writeObject("transient 修饰的sTr 在writeExternal中保存的值"); } public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { System.out.println("Blip1.readExternal"); i = in.readInt(); s = (String) in.readObject(); //sTr = (String) in.readObject(); } public String toString() { return String.format("i=%d,s=%s,sTr=%s",i,s,sTr); } } class Blip2 implements Externalizable { Blip2() //这个构造器不是public,序列化可以正常进行,反序列化时报异常. { System.out.println("Blip2 Constructor"); } public void writeExternal(ObjectOutput out) throws IOException { System.out.println("Blip2.writeExternal"); } public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { System.out.println("Blip2.readExternal"); } } public class Blips { public static void main(String[] args) throws IOException, ClassNotFoundException { System.out.println("Constructing objects:"); Blip1 b1 = new Blip1(); Blip2 b2 = new Blip2(); ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream("D:\\D\\Blips.out")); System.out.println("\nSaving objects:"); o.writeObject(b1); o.writeObject(b2); o.close(); // Now get them back: ObjectInputStream in = new ObjectInputStream(new FileInputStream("D:\\D\\Blips.out")); System.out.println("\nRecovering b1:"); b1 = (Blip1) in.readObject(); System.out.println(b1); //由这里的输出可知 //(1) 先输出 "初始化块中的初始化" 表示 反序列化时先调用初始化块 //(2) 输出Blip1 Constructor 表示 反序列化时要调用 默认public 构造器 //(3) Blip1.readExternal 表示调用了readExternal 方法 //(4) 'i=123,s=s在writeExternal中保存的值,sTr=transient初始的值' 中i值是在readExternal中恢复的 // s字段 值也是在readExternal恢复的,而 sTr字段 依然保存了字段初始化时的值,说明实现了Externalizable接口 // 的对象在反序列化时会先根据class的定义初始化对象的字段,并且transient修饰的字段也会初始化 // ! System.out.println("Recovering b2:"); //b2 = (Blip2)in.readObject(); //去掉上面注释,执行时会报异常java.io.InvalidClassException: io.Blip2; io.Blip2; no valid constructor } } /** * 输出为: * Constructing objects: 静态 初始化块中的初始化 初始化块中的初始化 Blip1 Constructor Blip2 Constructor Saving objects: Blip1.writeExternal Blip2.writeExternal Recovering b1: 初始化块中的初始化 Blip1 Constructor Blip1.readExternal i=123,s=s在writeExternal中保存的值,sTr=transient初始的值 * */
赞 赏
微信赞赏 支付宝赞赏
本文固定链接: https://www.jack-yin.com/coding/thinking-in-java/2053.html | 边城网事
【下一篇】每日一句- He who wants to do good knocks at the gate; he who loves finds the gate open