java序列化与反序列化进阶(一)

一、readObject和writeObject

    通过上个章节的Java序列化与反序列化入门理解,对序列化和反序列化应该有了比较基本的认识。回顾一下,之前的序列化和反序列化,只是简单的处理,如果需要二次加工需要如何处理?比如序列化的时候需要对数据进行加密操作,对应的反序列化时候需要进行解密操作等。先看下面的例子:

1package com.test; 2 3import java.io.IOException; 4import java.io.ObjectInputStream; 5import java.io.ObjectOutputStream; 6import java.io.Serializable; 7 8public class Person implements Serializable{ 9 10 private static final long serialVersionUID = 3482314192692351792L; 11 private int weight; 12 private int age; 13 private String name; 14 public int getWeight() { 15 return weight; 16 } 17 public void setWeight(int weight) { 18 this.weight = weight; 19 } 20 public int getAge() { 21 return age; 22 } 23 public void setAge(int age) { 24 this.age = age; 25 } 26 public String getName() { 27 return name; 28 } 29 public void setName(String name) { 30 this.name = name; 31 } 32 33 /** 34  * 新增加的方法,序列化时会调用 35  * @param stream 36  * @throws IOException 37  */ 38 private void writeObject(ObjectOutputStream stream)throws IOException{ 39 System.out.println("invoke writeObject......"); 40 //此时进行对象的序列化 41 stream.defaultWriteObject(); 42 //额外序列化的数值,反序列化的时候会使用 43 stream.writeInt(12345); 44 } 45 46 /** 47  * 新增加的方法,反序列化时会调用 48  * @param stream 49  * @throws IOException 50  * @throws ClassNotFoundException 51  */ 52 private void readObject(ObjectInputStream stream)throws IOException,ClassNotFoundException{ 53 System.out.println("invoke readObject......"); 54 //此时进行默认的序列化 55 stream.defaultReadObject(); 56 System.out.print("默认初始化后的对象:"); 57 System.out.println(this); 58 this.age = stream.readInt(); 59 System.out.print("对象的age进行加工后:"); 60 } 61 62 @Override 63 public String toString() { 64 return "[weight:"+weight+",name:"+name+",age:"+age+"]"; 65 } 66} 67 68测试代码: 69import java.io.FileInputStream; 70import java.io.FileOutputStream; 71import java.io.ObjectInputStream; 72import java.io.ObjectOutputStream; 73import java.util.Arrays; 74 75import com.test.Person; 76import com.test.User; 77 78public class TestPerson { 79 80 /** 81  * 序列化 82  *  83  * @param filePath 84  *            序列化要写入的文件路径 85  * @throws Exception 86  */ 87 public static void writeObject(String filePath) throws Exception { 88 Person p = new Person(); 89 p.setAge(18); 90 p.setName("testPersonName"); 91 p.setWeight(120); 92 93 ObjectOutputStream oos = null; 94 try { 95 oos = new ObjectOutputStream(new FileOutputStream(filePath)); 96 oos.writeObject(p); 97 oos.flush(); 98 } finally { 99 if (oos != null) { 100 oos.close(); 101 } 102 } 103 } 104 105 /** 106  * 反序列化 107  *  108  * @param filePath 109  *            序列化的文件 110  * @throws Exception 111  */ 112 public static void readObject(String filePath) throws Exception { 113 ObjectInputStream ois = null; 114 try { 115 ois = new ObjectInputStream(new FileInputStream(filePath)); 116 Person p = (Person) ois.readObject(); 117 System.out.println(p); 118 } finally { 119 if (ois != null) { 120 ois.close(); 121 } 122 } 123 } 124 125 public static void test(long num) { 126 byte[] byteNum = new byte[8]; 127 for (int ix = 0; ix < 8; ++ix) { 128 int offset = 64 - (ix + 1) * 8; 129 byteNum[ix] = (byte) ((num >> offset) & 0xff); 130 } 131 System.out.println(Arrays.toString(byteNum)); 132 } 133 134 public static void main(String[] args) throws Exception { 135 String filePath = "f:/obj.out"; 136 writeObject(filePath); 137 readObject(filePath); 138 } 139}

通过以上的例子可以看出,在序列化的时候程序执行了对象中定义的私有方法writeObject,反序列化的时候则执行了readObject。由于方法都是私有的,所以不难猜测,肯定是用了反射的方式实现。查看ObjectOutputStream中的writeSerialData方法,以及ObjectInputStream中的readSerialData方法即可发现反射的调用。

所以,通过在writeObject和readObject中二次处理,可以达到加工的目的。

二、Externalizable

    对象序列化可以继承Serializable,也可以继承Externalizable,两者有何区别呢?我们试着将上面Person的继承修改成Externalizable并运行,如下:

1package com.test; 2 3import java.io.Externalizable; 4import java.io.IOException; 5import java.io.ObjectInput; 6import java.io.ObjectInputStream; 7import java.io.ObjectOutput; 8import java.io.ObjectOutputStream; 9 10public class Person implements Externalizable{ 11 12 private static final long serialVersionUID = 3482314192692351792L; 13 private int weight; 14 private int age; 15 private String name; 16 public Person(){ 17 System.out.println("invoke constructor!"); 18 } 19 20 public int getWeight() { 21 return weight; 22 } 23 public void setWeight(int weight) { 24 this.weight = weight; 25 } 26 public int getAge() { 27 return age; 28 } 29 public void setAge(int age) { 30 this.age = age; 31 } 32 public String getName() { 33 return name; 34 } 35 public void setName(String name) { 36 this.name = name; 37 } 38 39 /** 40  * 新增加的方法,序列化时会调用 41  * @param stream 42  * @throws IOException 43  */ 44 private void writeObject(ObjectOutputStream stream)throws IOException{ 45 System.out.println("invoke writeObject......"); 46 //此时进行对象的序列化 47 stream.defaultWriteObject(); 48 //额外序列化的数值,反序列化的时候会使用 49 stream.writeInt(12345); 50 } 51 52 /** 53  * 新增加的方法,反序列化时会调用 54  * @param stream 55  * @throws IOException 56  * @throws ClassNotFoundException 57  */ 58 private void readObject(ObjectInputStream stream)throws IOException,ClassNotFoundException{ 59 System.out.println("invoke readObject......"); 60 //此时进行默认的序列化 61 stream.defaultReadObject(); 62 System.out.print("默认初始化后的对象:"); 63 System.out.println(this); 64 this.age = stream.readInt(); 65 System.out.print("对象的age进行加工后:"); 66 } 67 68 @Override 69 public String toString() { 70 return "[weight:"+weight+",name:"+name+",age:"+age+"]"; 71 } 72 @Override 73 public void readExternal(ObjectInput in) throws IOException, 74 ClassNotFoundException { 75 76 } 77 @Override 78 public void writeExternal(ObjectOutput out) throws IOException { 79 80 } 81} 82 83重新运行测试代码,结果如下: 84invoke constructor! 85invoke constructor! 86[weight:0,name:null,age:0]

通过以上的例子,说明程序并没有进行序列化;另外,无参构造函数调用了两次,说明序列化和反序列化的时候各调用了一次。重新修改代码,使其可以序列化,如下:

1package com.test; 2 3import java.io.Externalizable; 4import java.io.IOException; 5import java.io.ObjectInput; 6import java.io.ObjectInputStream; 7import java.io.ObjectOutput; 8import java.io.ObjectOutputStream; 9 10public class Person implements Externalizable{ 11 12 private static final long serialVersionUID = 3482314192692351792L; 13 private int weight; 14 private int age; 15 private String name; 16 public Person(){ 17 System.out.println("invoke constructor!"); 18 } 19 20 public int getWeight() { 21 return weight; 22 } 23 public void setWeight(int weight) { 24 this.weight = weight; 25 } 26 public int getAge() { 27 return age; 28 } 29 public void setAge(int age) { 30 this.age = age; 31 } 32 public String getName() { 33 return name; 34 } 35 public void setName(String name) { 36 this.name = name; 37 } 38 39 /** 40  * 新增加的方法,序列化时会调用 41  * @param stream 42  * @throws IOException 43  */ 44 private void writeObject(ObjectOutputStream stream)throws IOException{ 45 System.out.println("invoke writeObject......"); 46 //此时进行对象的序列化 47 stream.defaultWriteObject(); 48 //额外序列化的数值,反序列化的时候会使用 49 stream.writeInt(12345); 50 } 51 52 /** 53  * 新增加的方法,反序列化时会调用 54  * @param stream 55  * @throws IOException 56  * @throws ClassNotFoundException 57  */ 58 private void readObject(ObjectInputStream stream)throws IOException,ClassNotFoundException{ 59 System.out.println("invoke readObject......"); 60 //此时进行默认的序列化 61 stream.defaultReadObject(); 62 System.out.print("默认初始化后的对象:"); 63 System.out.println(this); 64 this.age = stream.readInt(); 65 System.out.print("对象的age进行加工后:"); 66 } 67 68 @Override 69 public String toString() { 70 return "[weight:"+weight+",name:"+name+",age:"+age+"]"; 71 } 72 @Override 73 public void readExternal(ObjectInput in) throws IOException, 74 ClassNotFoundException { 75 this.name = (String)in.readObject(); 76 } 77 @Override 78 public void writeExternal(ObjectOutput out) throws IOException { 79 out.writeObject(this.name); 80 } 81}

此时name字段可以正常序列化。使用Externalizable时切记要提供无参构造!

三、readResolve方法的使用

根据测试,每次反序列的对象应该是为重新创建的,所以执行==操作时会返回false。那么如果我们需要对象是单例的情况下,该怎么操作呢?readResolve()可以很好的解决这个问题。先看一下未使用之前,Person类如下:

1package com.test; 2 3import java.io.IOException; 4import java.io.ObjectInputStream; 5import java.io.ObjectOutputStream; 6import java.io.Serializable; 7 8public class Person implements Serializable{ 9 10 private static final Person PERSON = new Person(1,2,"123"); 11 private static final long serialVersionUID = 3482314192692351792L; 12 private int weight; 13 private int age; 14 private String name; 15 public Person(){ 16 17 } 18 19 public Person(int age,int weight,String name){ 20 this.age = age; 21 this.weight = weight; 22 this.name = name; 23 } 24 25 public static Person getSingleton(){ 26 return PERSON; 27 } 28 29 public int getWeight() { 30 return weight; 31 } 32 public void setWeight(int weight) { 33 this.weight = weight; 34 } 35 public int getAge() { 36 return age; 37 } 38 public void setAge(int age) { 39 this.age = age; 40 } 41 public String getName() { 42 return name; 43 } 44 public void setName(String name) { 45 this.name = name; 46 } 47 48 /** 49  * 新增加的方法,序列化时会调用 50  * @param stream 51  * @throws IOException 52  */ 53 private void writeObject(ObjectOutputStream stream)throws IOException{ 54 //此时对静态的Person进行序列化 55 stream.writeObject(PERSON); 56 } 57 58 /** 59  * 新增加的方法,反序列化时会调用 60  * @param stream 61  * @throws IOException 62  * @throws ClassNotFoundException 63  */ 64 private void readObject(ObjectInputStream stream)throws IOException,ClassNotFoundException{ 65         //反序列化Person 66 stream.readObject(); 67 } 68 69 @Override 70 public String toString() { 71 return "[weight:"+weight+",name:"+name+",age:"+age+"]"; 72 } 73}

测试方法进行了调整,增加了对Person.PERSON和反序列化完的对象的比较:

1import java.io.FileInputStream; 2import java.io.FileOutputStream; 3import java.io.ObjectInputStream; 4import java.io.ObjectOutputStream; 5import java.util.Arrays; 6 7import com.test.Person; 8import com.test.User; 9 10public class TestPerson { 11 12 /** 13  * 序列化 14  *  15  * @param filePath 16  *            序列化要写入的文件路径 17  * @throws Exception 18  */ 19 public static void writeObject(String filePath) throws Exception { 20 Person p = new Person(); 21 p.setAge(18); 22 p.setName("testPersonName"); 23 p.setWeight(120); 24 25 ObjectOutputStream oos = null; 26 try { 27 oos = new ObjectOutputStream(new FileOutputStream(filePath)); 28 oos.writeObject(p); 29 oos.flush(); 30 } finally { 31 if (oos != null) { 32 oos.close(); 33 } 34 } 35 } 36 37 /** 38  * 反序列化 39  *  40  * @param filePath 41  *            序列化的文件 42  * @throws Exception 43  */ 44 public static void readObject(String filePath) throws Exception { 45 ObjectInputStream ois = null; 46 try { 47 ois = new ObjectInputStream(new FileInputStream(filePath)); 48 Person p = (Person) ois.readObject(); 49 System.out.println(== Person.getSingleton()); 50 } finally { 51 if (ois != null) { 52 ois.close(); 53 } 54 } 55 } 56 57 public static void test(long num) { 58 byte[] byteNum = new byte[8]; 59 for (int ix = 0; ix < 8; ++ix) { 60 int offset = 64 - (ix + 1) * 8; 61 byteNum[ix] = (byte) ((num >> offset) & 0xff); 62 } 63 System.out.println(Arrays.toString(byteNum)); 64 } 65 66 public static void main(String[] args) throws Exception { 67 String filePath = "f:/obj.out"; 68 writeObject(filePath); 69 readObject(filePath); 70 } 71}

运行,结果为false,说明不是同一个对象。接下来对Person进行调整,新增readResolve方法,如下:

1package com.test; 2 3import java.io.IOException; 4import java.io.ObjectInputStream; 5import java.io.ObjectOutputStream; 6import java.io.ObjectStreamException; 7import java.io.Serializable; 8 9public class Person implements Serializable{ 10 11 private static final Person PERSON = new Person(1,2,"123"); 12 private static final long serialVersionUID = 3482314192692351792L; 13 private int weight; 14 private int age; 15 private String name; 16 public Person(){ 17 18 } 19 20 public Person(int age,int weight,String name){ 21 this.age = age; 22 this.weight = weight; 23 this.name = name; 24 } 25 26 public static Person getSingleton(){ 27 return PERSON; 28 } 29 30 public int getWeight() { 31 return weight; 32 } 33 public void setWeight(int weight) { 34 this.weight = weight; 35 } 36 public int getAge() { 37 return age; 38 } 39 public void setAge(int age) { 40 this.age = age; 41 } 42 public String getName() { 43 return name; 44 } 45 public void setName(String name) { 46 this.name = name; 47 } 48 49 /** 50  * 新增加的方法,序列化时会调用 51  * @param stream 52  * @throws IOException 53  */ 54 private void writeObject(ObjectOutputStream stream)throws IOException{ 55 //此时进行对象的序列化 56 stream.writeObject(PERSON); 57 } 58 59 /** 60  * 新增加的方法,反序列化时会调用 61  * @param stream 62  * @throws IOException 63  * @throws ClassNotFoundException 64  */ 65 private void readObject(ObjectInputStream stream)throws IOException,ClassNotFoundException{ 66 stream.readObject(); 67 } 68 69 private Object readResolve() throws ObjectStreamException {   70 //直接返回静态的Person 71        return PERSON;   72    }  73 74 @Override 75 public String toString() { 76 return "[weight:"+weight+",name:"+name+",age:"+age+"]"; 77 } 78}

此时再次执行测试代码,返回true,说明为同一个对象。同样的,查看ObjectInputStream,可以查看发射调用了readResolve方法。

上一篇:Java序列化与反序列化入门理解

下一篇:java序列化与反序列化进阶(二)

点赞
收藏

评论区

加载中...

相关推荐

javaBean为什么要implements Serializable

一个对象序列化的接口,一个类只有实现了Serializable接口,它的对象才是可序列化的。因此如果要序列化某些类的对象,这些类就必须实现Serializable接口。而实际上,Serializable是一个空接口,没有什么具体内容,它的目的只是简单的标识一个类的对象可以被序列化。    什么情况下需要序列化:    1.     当

解决Redis序列化Java8的LocalDateTime问题

在从Redis获取带有LocalDateTime类型属性的对象时,产生序列化和反序列化问题解决办法方式一:实体类上指定LocalDateTime的序列化器和反序列化器java@JsonDeserialize(usingLocalDateTimeDeserializer.class)//反序列化@JsonSerialize(usingLo

Gson 数据解析

gson和其他现有javajson类库最大的不同时gson需要序列化的实体类不需要使用annotation来标识需要序列化的字段,同时gson又可以通过使用annotation来灵活配置需要序列化的字段。下面是一个简单的例子:1.public class Person {3.private String name;

unity 序列化和反序列化

什么是序列化和反序列化(1)序列化是指把对象转换为字节序列的过程,而反序列化是指把字节序列恢复为对象的过程;. (2)序列化:对象序列化的最主要的用处就是在传递和保存对象的时候,保证对象的完整性和可传递性。序列化是把对象转换成有序字节流,以便在网络上传输或者保存在本地文件中。序列化后的字节流保存了对象的状态以及相关的描述信息。序列化机制

JFinal的Model进行json反序列化

在使用JFinal进行开发时,我们可能会需要解决这样的问题:Model进行json的序列化与反序列化。官方已经提供了序列化的方法Model.toJson()非常方便,反序列化就得自己实现一下了。之前我一直都是把Model序列化成的json字符串,反序列化成map,然后再调用Model.setAttrs(map)。这样就有类型转换问题,最后反序列化得到

Go 中 JSON 的序列化和反序列化

golang中对json的序列化/反序列化操作还是比较容易的,序列化操作主要是通过encoding/json包的Marshal()方法来实现,反序列化操作主要是通过encoding/json包的Unmarshal()方法来实现.//JSON序列化和反序列化//可用在api序列化输出//转成

java序列化与反序列化进阶(一) - HelloWorld