一个类实现Serializable接口后可以被序列化。这个接口没有方法和字段,只是用来标志这个类可以被序列化。
如果父类实现了serializable接口,那么子类实现还是不实现接口都一样。子类和父类所有的非static,非transient的字段的值都能被保存和恢复。
如果父类没有实现serializable接口,那么父类必须有无参的且可被子类访问的构造函数,但是不会保存父类的所有字段的值。
在反序列化的时候,没有实现serializable接口的类将会调用无参的构造函数进行实例化。而无参的构造函数必须可以被子类(进行反序列化的类)访问,子类的字段都会被恢复。
如果父类不实现 Serializable接口的话,就需要有默认的无参的构造函数。这是因为一个 Java 对象的构造必须先有父对象,才有子对象,反序列化也不例外。在反序列化时,为了构造父对象,只能调用父类的无参构造函数作为默认的父对象。因此当我们取父对象的变量值时,它的值是调用父类无参构造函数后的值。在这种情况下,在序列化时根据需要在父类无参构造函数中对变量进行初始化,否则的话,父类变量值都是默认声明的值,如 int 型的默认是 0,string 型的默认是 null。
如果需要在序列化和反序列化时做特殊处理,那么类中需要有这几个方法
private void writeObject(java.io.ObjectOutputStream out) throws IOException
private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException;
private void readObjectNoData() throws ObjectStreamException;
writeObject可以记录一个类的状态信息用于readObject恢复。在这个方法中可以调用out.defaultWriteObject来记录默认的类信息。
如果被序列化的类中有Object writeReplace()方法,那么在序列化的时候,会被调用。用来替换当前类
读取一个类的class信息,类签名,非transient的,非静态的字段值。
objectInputStream.readObject();
父类:
public class ParentPer {
private String p1;
private static String p2;
protected int p3;
public int p4;
ParentPer(){
System.out.println("父类构造方法执行了");
}
@Override
public String toString() {
return p1+"_"+p2+"_"+p3+"_"+p4;
}
public String getP1() {
return p1;
}
public void setP1(String p1) {
this.p1 = p1;
}
public static String getP2() {
return p2;
}
public static void setP2(String p2) {
ParentPer.p2 = p2;
}
}
子类:
public class ChildPtr extends ParentPer implements Serializable{
private String c1;
private static String c2;
protected int c3;
public int c4;
public ChildPtr(String c1, String c2) {
super();
this.c1 = c1;
ChildPtr.c2 = c2;
super.setP1("p1");
setP2("p2");
p3=5;
p4=6;
}
@Override
public String toString() {
return c1+"_"+c2+"_"+c3+"_"+c4+" "+super.toString();
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
ChildPtr childPtr = new ChildPtr("c1", "c2");
childPtr.c3=9;
childPtr.c4=10;
byte[] c = SerializableUtil.serialiable(childPtr);
ByteArrayInputStream bi = new ByteArrayInputStream(c);
ObjectInputStream objectInputStream = new ObjectInputStream(bi);
ChildPtr cc = (ChildPtr) objectInputStream.readObject();
System.out.println(childPtr);
System.out.println(cc);
}
}
serialVersionUID字段表示类的序列化版本,用于反序列化时校验。如果反序列化时的类的serialVersionUID与序列化时不同,那么会抛出InvalidClassException异常。
必须是final和static修饰的,推荐使用private修饰,因为它不需要被继承使用,只在序列化和反序列化时使用。
ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L;
如果类中没有这个字段,那么在运行时jvm会帮忙计算一个值。推荐用户给每一个有序列化能力的类明确指定一个serialVersionUID 。因为默认的计算方式是严重依赖于编译器的实现,可能导致反序列化的时候抛出InvalidClassException异常。
数组类型不能明确指定serialVersionUID,所以它们使用默认的计算值,但是反序列化的时候不需要校验serialVersionUID。