Java自定义序列化
2014-05-28
我们知道,通过实现java.io.Serializable
接口可以使得该类的实例能够被序列化。例如如下的Person
类,
import java.io.Serializable;
public class Person implements Serializable {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return String.format("My name is %s, and I'm %d years old.",
name, age);
}
}
通过如下的代码
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class Serializing {
public static void main(String ...flags) throws Exception {
if (flags.length != 1) {
System.exit(1);
return;
}
String flag = flags[0];
if ("-o".equals(flag)) {
Person alice = new Person("Alice", 10);
ObjectOutputStream out = new ObjectOutputStream(System.out);
out.writeObject(alice);
} else if ("-i".equals(flag)) {
ObjectInputStream in = new ObjectInputStream(System.in);
Person person = (Person) in.readObject();
System.out.println(person);
} else {
System.err.printf("unknown flag: %s%n", flag);
System.exit(1);
}
}
}
编译过后,运行命令
java -cp target/classes/ Serializing -o | java -cp target/classes/ Serializing -i
就会打印出
My name is Alice, and I'm 10 years old.
在这个示例中,我借助了管道来连接输出流与输入流:Person
类的alice
实例先是通过对象输出流(基于System.out
)输出,再由对象输入流(基于System.in
)读入。在对象的序列化过程,ObjectOutputStream.writeObject
与ObjectInputStream.readObject
方法将对象以Java默认的序列化方式实现了alice
实例与二进制流之间的转换。
但是有时候,可能是由于独特的序列化需求、性能方面的考虑又或是希望在对象反序列化后能够执行其他操作,我们需要重写这个默认的序列化实现。这时候,我们可以通过如下两个方法来实现。
private void writeObject(ObjectOutputStream out) throws IOException
private void readObject(ObjectInputStream in) throws ClassNotFoundException, IOException
这两个私有方法分别实现了对象的序列化与反序列化操作,完整例子如下。
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.nio.charset.Charset;
public class Person implements Serializable {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return String.format("My name is %s, and I'm %d years old.",
name, age);
}
private void writeObject(ObjectOutputStream out) throws IOException {
int strLen = (name == null) ? -1 : name.length();
out.writeInt(strLen);
if (strLen > 0) {
out.write(name.getBytes(Charset.forName("UTF-8")));
}
out.writeInt(age);
}
private void readObject(ObjectInputStream in) throws
ClassNotFoundException, IOException {
int strLen = in.readInt();
if (strLen <= -1) {
name = null;
} else if (strLen == 0) {
name = "";
} else {
byte[] strBytes = new byte[strLen];
in.readFully(strBytes);
name = new String(strBytes, Charset.forName("UTF-8"));
}
age = in.readInt();
}
}
此时,ObjectOutputStream.writeObject
以及ObjectInputStream.readObject
就会分别使用我们自定义的私有writeObject
以及readObject
方法来做序列化,而不是使用用默认的序列化实现。由于这两个方法是私有的,那就意味着Person
的子类序列化方法既不会继承,也不会覆盖。也就是说,对于整个继承层次中的类,都会从父类至子类依次调用序列化操作。但是有的时候,我们希望子类复用父类的序列化实现,又或者子类重写父类的序列化实现,那么这时候我们就需要用到java.io.Externalizable
接口了。
与java.io.Serializable
接口中的那两个私有方法类似,java.io.Externalizable
接口具有两个公有的抽象方法:
public void writeExternal(ObjectOutput out) throws IOException
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException
使用Externalizable
接口的Person
类如下。
import java.io.*;
import java.nio.charset.Charset;
public class Person implements Externalizable {
private String name;
private int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return String.format("My name is %s, and I'm %d years old.",
name, age);
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
int strLen = (name == null) ? -1 : name.length();
out.writeInt(strLen);
if (strLen > 0) {
out.write(name.getBytes(Charset.forName("UTF-8")));
}
out.writeInt(age);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
int strLen = in.readInt();
if (strLen <= -1) {
name = null;
} else if (strLen == 0) {
name = "";
} else {
byte[] strBytes = new byte[strLen];
in.readFully(strBytes);
name = new String(strBytes, Charset.forName("UTF-8"));
}
age = in.readInt();
}
}
这里,我们发现除了添加了Externalizable
接口的两个公有方法之外,还有一处发生了变化,Person
添加了一个默认的构造函数。
当使用Externalizable
进行反序列化构造对象时,会调用该类的默认构造函数进行构造。如果没有默认构造函数,那么将会在反序列化的时候抛出java.io.InvalidClassException
。
由于Externalizable
使用的是接口的方式进行序列化,所以对于整个继承层次中的类,序列化时只会调用叶节点的类。如果想要在父类的基础上扩展子类的序列化,那么就需要在子类的方法中调用父类的方法。所以,相比使用Serializable
的私有方法自定义序列化,Externalizable
更加的可控。