发现网络上对于反序列化时,什么时候需要空参构造器都模糊不清,也没有一个准确的一个概念,因此我们由现象到源码一探究竟

先上一个父类和子类都没有空参构造器,父类实现Serializable接口,子类间接实现Serializable接口,序列化子类对象sub

class Data implements Serializable{
private int n;
public Data(int n){
this.n = n;
}
public String toString(){
return Integer.toString(n);
}
}
class sub extends Data{
private static int m = 6;
private int k;
public sub(int n) {
super(n);
}
}
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("worm.out"));
sub d = new sub(10);
out.writeObject(d);
ObjectInputStream in = new ObjectInputStream(new FileInputStream("worm.out"));
sub f = (sub) in.readObject();

序列化成功了,说明这种情况父类子类不需要空参构造器,序列化出来的文件是这样的

¬í �sr main.subbÊ��uéªP� �I �kxr main.Dataç��Âût÷?� �I �nxp

接下来尝试父类和子类都没有空参构造器,父类不实现Serializable接口,子类实现Serializable接口,序列化子类对象sub

class Data{
private int n;
public Data(int n){
this.n = n;
}
public String toString(){
return Integer.toString(n);
}
}
class sub extends Data implements Serializable{
private static int m = 6;
private int k;
public sub(int n) {
super(n);
}
}
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("worm.out"));
sub d = new sub(10);
out.writeObject(d);
ObjectInputStream in = new ObjectInputStream(new FileInputStream("worm.out"));
sub f = (sub) in.readObject();

结果会发现提示找不到可用的构造器

Exception in thread "main" java.io.InvalidClassException: main.sub; no valid constructor
at java.io.ObjectStreamClass$ExceptionInfo.newInvalidClassException(ObjectStreamClass.java:150)
at java.io.ObjectStreamClass.checkDeserialize(ObjectStreamClass.java:790)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1782)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1353)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:373)
at main.Worm.main(Worm.java:81)

序列化出的文件是这样的

¬í �sr main.subµ|�)CO�I� �I �kxp

会什么会出现这种情况呢,最后我们追踪到ObjectStreamClass类的一个方法,是引起异常的主要原因

//ObjectStreamClass.java
/**
* Returns subclass-accessible no-arg constructor of first non-serializable
* superclass, or null if none found. Access checks are disabled on the
* returned constructor (if any).
*/
private static Constructor> getSerializableConstructor(Class> cl) {
Class> initCl = cl;
while (Serializable.class.isAssignableFrom(initCl)) {
if ((initCl = initCl.getSuperclass()) == null) {
return null;
}
}
try {
Constructor> cons = initCl.getDeclaredConstructor((Class>[]) null);
int mods = cons.getModifiers();
if ((mods & Modifier.PRIVATE) != 0 ||
((mods & (Modifier.PUBLIC | Modifier.PROTECTED)) == 0 &&
!packageEquals(cl, initCl)))
{
return null;
}
cons = reflFactory.newConstructorForSerialization(cl, cons);
cons.setAccessible(true);
return cons;
} catch (NoSuchMethodException ex) {
return null;
}
}

方法的注释是

Returns subclass-accessible no-arg constructor of first non-serializable superclass, or null if none found. Access checks are disabled on the returned constructor (if any)。

翻译过来的意思就是,返回第一个非可序列化超类的子类可访问的no-arg构造函数,如果没有找到则返回null。在返回的构造函数上禁用访问检查(如果有的话)。

因此我们就明白了

为什么在第一个例子中没有丢出InvalidClassException异常,因为第一个例子的第一个非可序列化超类是Object,Object没有显式的构造方法,因此就是默认有默认的空参构造方法,符合条件

第二个例子中丢出InvalidClassException异常是因为第二个例子的第一个非可序列化超类是Date,因为Date没有显示的构造方法,因此丢出异常

最后我们验证一下,如果给Date类加上默认空参构造方法,是否还能编译通过

class Data{
private int n;
public Data(int n){
this.n = n;
}
public Data(){
this.n = 10;
}
public String toString(){
return Integer.toString(n);
}
}

最后返回成功了,这也验证了我们的想法。

最后我们得到的结论是,序列化时,如果第一个非可序列化超类非Object类,那么一定要预留一个空参构造方法,否则就会序列化失败