自定义序列化小记
对于实现了Serializable接口的类,在对其对象进行序列化的时候,会自动将该对象的所有实例变量依次进行序列,特别是在某个实例变量应用到了其他对象时,表现为递归式的序列化机制。这种“一概而全”序列化方式往往不是我们实际想要的,因此有必以自定义的方式来序列化(具体地讲比如控制某些实例变量可以序列化,增加一些处理逻辑进行序列化等等)。有以下几种方式来实现自动以的序列化。
1、使用transient关键字
使用transient关键字修饰实例变量。从语义上讲transient是短暂的、瞬态的意思,因此不适合用序列化策略来存储。在序列化的时候该实例变量不会被写进字节序列,相当于会忽略掉该变量的序列化。这是最简单方便的自定义序列化方式,在JDK源码中也有很多地方用到了该关键字来修饰的实例变量。
使用transient关键字实现自定义序列化有几件事需要了解:
a、transient只应该用来修饰成员变量,不应修饰类变量。虽然在语法上用transient修饰静态变量不会报错,但是这样做没有效果也没有意义:类变量本身就是隶属于类,不属于任何一个对象。
b、反序列化后,之前被transient修饰的变量的值被赋予系统设定的默认初值(同时注意:反序列化的过程不会调用对象的任何构造器)。
c、实现了Externalizable接口
package com.prac; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; public class TestSeri{ public static void main(String[] args) { String path = System.getProperty("user.dir")+"\\target.md"; Target target = new Target("hi"); try { //序列化 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(path)); oos.writeObject(target); //反序列化 ObjectInputStream ois = new ObjectInputStream(new FileInputStream(path)); Target othetarget = (Target)ois.readObject(); Target.staticVar += " world!";//改变Target类的静态变量 System.out.println("instVar = "+othetarget.instVar); //输出了"hello world!",静态变量不参与序列化 System.out.println("staticVar = "+othetarget.staticVar); //以下成员变量未参与序列化,反序列化后赋予系统设定的初始值 System.out.println("intValue = "+othetarget.intValue);//0 System.out.println("doubleValue = "+othetarget.doubleValue);//0.0 System.out.println("booValue = "+othetarget.booValue);//fasle System.out.println("stringValue = "+othetarget.stringValue);//null System.out.println("objValue = "+othetarget.objValue);//null } catch (Exception e) { // TODO: handle exception e.printStackTrace(); } } } class Target implements Serializable{ public static String staticVar = "hello"; // public transient static String staticValue = "hello";//语法上可以用transient修饰静态变量,但无意义 public String instVar = ""; public transient double doubleValue = 10.0; public transient int intValue = 100; public transient boolean booValue = true; public transient String stringValue = "hello world"; public transient Object objValue = new Object(); public Target(){ System.out.println("invoke Target()"); } public Target(String instVar){ this.instVar = instVar; System.out.println("invoke Target(String instVar)"); } }
2、使用特殊签名的方法
在类中定义如下三个特殊签名的方法,可以按照自定义的逻辑来实现自定义序列化。
private void writeObject(ObjectOutputStream oos) throws IOException private void readObject(ObjectInputStream ois) throws IOException,ClassNotFoundException private void readObjectNoData() throws ObjectStreamException
一个简单的示例如下:
package com.prac; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; public class TestSeri{ public static void main(String[] args) { String path = System.getProperty("user.dir")+"\\target.md"; User user = new User("qcer","123456"); try { //序列化 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(path)); oos.writeObject(user); //反序列化 ObjectInputStream ois = new ObjectInputStream(new FileInputStream(path)); User otheuser = (User)ois.readObject(); System.out.println("username = "+otheuser.username); System.out.println("password = "+otheuser.password); } catch (Exception e) { // TODO: handle exception e.printStackTrace(); } } } class User implements Serializable{ public String username = ""; public String password = ""; public User(){ } public User(String username,String password){ this.username = username; this.password = password; } private void writeObject(ObjectOutputStream oos) throws IOException{ oos.writeObject(username); oos.writeObject(encrypt(password)); } private void readObject(ObjectInputStream ois) throws IOException,ClassNotFoundException{ this.username = (String)ois.readObject(); this.password = dencrypt((String)ois.readObject()); } private String encrypt(String plaintext){ String ciphertext = ""; //...省略加密算法部分 return ciphertext; } private String dencrypt(String ciphertext){ String plaintext = ""; //...省略解密算法部分 return plaintext; } }
3、实现Externalizable接口
Externalizable实接口际上是继承了Serializable接口
实现Externalizable接口的类中需要实现writeExternal(ObjectOutput out)和readExternal(ObjectInput in)两个方法,同之前的一样,可以用out.writeXXX()和in.readXXX()的方式来自定义序列化和反序列化数据。
一个示例如下:
package com.prac; import java.io.*; public class TestExseri{ public static void main(String[] args) { String path = System.getProperty("user.dir")+"\\book.md"; Book book = new Book("Thinking in Java",108.00); try { //序列化 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(path)); oos.writeObject(book); //反序列化,会调用Book类的无参构造器 ObjectInputStream ois = new ObjectInputStream(new FileInputStream(path)); Book otherbook = (Book)ois.readObject(); System.out.println("name = "+otherbook.name);//Thinking in Java System.out.println("price = "+otherbook.price);//108.0 } catch (Exception e) { // TODO: handle exception e.printStackTrace(); } } } class Book implements Externalizable{ public transient String name = ""; public double price = 0.0; public Book(){ System.out.println("invoke Book()"); } public Book(String name,double price) { this.name = name; this.price = price; } @Override public void writeExternal(ObjectOutput out) throws IOException { out.writeObject(name); out.writeDouble(price); } @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { this.name = (String)in.readObject(); this.price = in.readDouble(); } }
实际上,ObjectOutputStream实现了ObjectOutput接口,而后者继承了DataOutput接口。