序列化的一般过程:
- 将对象实例相关的类元数据输出
- 递归的输出类的超类描述直到不再有超类
- 从最顶层的超类开始输出对象实例的实际数据
- 从上至下递归输出实例的数据
通过以下例子来验证以上四个过程是否正确
class father {
String nameFather = "nameFather";
}
class sun extends father implements Serializable{
public String name = "nameSun";
}
class suner extends sun{
}
suner test;
System.out.println(test.nameFather);
输出结果
nameFather
输出解释:
1.在序列化的过程中,不管是子类实现了序列化接口,还是父类实现了序列化接口,子类在序列化时,都会将父类与子类的属性写入流中。
2.查看序列化的文件内容可知,序列化时,从顶层超类到本类输出类描述,然后将各属性的值写入流(属性值按照从父类到子类的顺序进行)。
对象包含其他对象
class sun implements Serializable {
public suner s = new suner();
}
class suner implements Serializable{
public String name = "nameSunner";
public int age = 10;
}
序列化sun,如果suner 没有实现Serializable接口,会抛出NotSerializableException异常。
此外反序列化时,sun必须有一个无参的构造函数。因为在反序列化sun时,会先产生一个 sun s = new sun() 然后通过反射机制填充其属性值。
单例模式的Serializable
考虑到Singleton模式下,一个类只能有一个实例。可是,通过反序列化,可以创建多个不同的实例。为了防止这一点,可以在类中添加readResolve方法
import java.io.*;
public class product {
public static void main(String[] args) {
try {
//序列化test1
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("a.txt"));
user test1 = user.getInstance();
out.writeObject(test1);
out.close();
test1.name = "nameChange";
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("a.txt"));
user test2 = (user) inputStream.readObject();
inputStream.close();
//判断是否是同一个对象
System.out.println(test1 == test2);
//输出应该是 nameChange
System.out.println(test2.name);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
class user implements Serializable {
private static final long serialVersionUID = -3458656943762485150L;
public String name = "nameInit";
public int age = 20;
private static user u = new user();
private user() {
}
public static user getInstance() {
return u == null ? new user() : u;
}
private Object readResolve() throws ObjectStreamException {
System.out.println("call readResolve");
return u;
}
}
输出结果
call readResolve
true
nameChange
输出解释:
1.反序化时,自动调用了readResolve方法。
2.因为test1和test3都是指向 user.u这个对象,因此,相等。
3.因为test1和test3都是指向 user.u这个对象,因此修改test1,就等同于在修改u。
综上所述,readResolve方法的确能保证Singleton模式的正确性。
Serializable的选择性序列化
将需要的属性序列化,不需要的则可以去掉,比如static变量(static在反序列化之后,使用的是jvm中当前的值,因此static属性的序列化在大多数时候没有用处)。
在不需要序列化的字段前加上transient。例如
transient public String name = "myname";
但是这种设计并不灵活,如果一个类被组合到多个其它的类中,而其他类需要序列化的字段都不相同,那只能呵呵了。
Externalizable是Serializable的子接口。一个类在实现Externalizable时,需要实现两个方法writeExternal和readExternal。
class sun implements Externalizable {
public suner s = new suner();
@Override
public void writeExternal(ObjectOutput out) throws IOException {
// TODO Auto-generated method stub
out.writeObject(s.name);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
// TODO Auto-generated method stub
s.name = (String) in.readObject();
}
}
class suner implements Serializable{
public String name = "nameSunner";
public int age = 10;
}
writeExternal中可以将需要的属性序列化到流中,此外还可以进行一个额外的操作,比如加密、回收内存等等。
综上所述,如果一个类需要在序列化时,进行一些额外的操作,或者不需要序列化所有属性时,可用Externalizable来完成所需要的功能。
Externalizable奇特的地方
Externalizable有一个奇怪的地方。在反序列化时,类必须具有一个public的无参构造函数。但是Serializable完全没有这种问题。
注:若一个类不是public,则默认的构造函数是frienly。
import java.io.*;
public class product {
public static void main(String[] args) {
try {
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("a.txt"));
sun test2 = (sun) inputStream.readObject();
System.out.println(test2.name);
inputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
class sun implements Externalizable {
public String name = "myname";
@Override
public void writeExternal(ObjectOutput out) throws IOException {
// TODO Auto-generated method stub
out.writeObject(name);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
// TODO Auto-generated method stub
name = (String) in.readObject();
}
}
异常信息:
java.io.InvalidClassException: bishi.sun; bishi.sun; no valid constructor
at java.io.ObjectStreamClass.checkDeserialize(ObjectStreamClass.java:713)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1733)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1329)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:351)
at bishi.product.main(product.java:13)
Caused by: java.io.InvalidClassException: bishi.sun; no valid constructor
at java.io.ObjectStreamClass.<init>(ObjectStreamClass.java:471)
at java.io.ObjectStreamClass.lookup(ObjectStreamClass.java:310)
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1106)
at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:326)
at bishi.product.main(product.java:10)
如果添加public构造函数则正常输出。