子类序列化父类需要满足两个核心条件:子类必须实现 serializable 接口,且父类必须有无参构造函数。当子类对象被序列化时,jvm 会自动调用父类的无参构造函数来恢复父类部分的字段状态,因此父类不能是抽象类或没有默认构造函数的具体类。

为什么父类必须有公共无参构造函数?
因为反序列化过程中,JVM 无法直接实例化父类对象,而是通过反射调用其无参构造函数来创建父类部分的状态。如果父类没有无参构造函数,或者该构造函数不是 public 的,反序列化时会抛出 InvalidClassException 或 InstantiationException。

import java.io.*;
// 父类:必须有无参构造函数
public class Parent implements Serializable {
private String name;
// 关键:必须提供公共无参构造函数
public Parent() {
this.name = "DefaultParent";
}
public Parent(String name) {
this.name = name;
}
@Override
public String toString() {
return "Parent{name='" + name + "'}";
}
}
// 子类:实现 Serializable
public class Child extends Parent implements Serializable {
private int age;
public Child(int age) {
super("CustomParent"); // 调用父类有参构造函数
this.age = age;
}
@Override
public String toString() {
return "Child{age=" + age + ", parent=" + super.toString() + "}";
}
public static void main(String[] args) throws Exception {
Child child = new Child(25);
System.out.println("Original: " + child);
// 序列化
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(child);
oos.close();
// 反序列化
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
Child deserializedChild = (Child) ois.readObject();
ois.close();
System.out.println("Deserialized: " + deserializedChild);
}
}
运行上述代码,输出结果为:
Original: Child{age=25, parent=Parent{name='CustomParent'}}
Deserialized: Child{age=25, parent=Parent{name='DefaultParent'}}
注意:父类字段 name 在反序列化后变为 "DefaultParent",因为父类无参构造函数被调用了,而父类的有参构造函数中的值未被保留。这是 Java 序列化的一个常见陷阱。
如何避免父类字段丢失?
可以通过在子类中实现 writeObject() 和 readObject() 方法来手动控制父类字段的序列化过程。这样可以在序列化前保存父类字段的值,并在反序列化后恢复它们。

import java.io.*;
public class ChildWithCustomSerialization extends Parent implements Serializable {
private int age;
public ChildWithCustomSerialization(int age) {
super("CustomParent");
this.age = age;
}
private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject();
out.writeObject(super.toString()); // 手动序列化父类状态
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
String parentState = (String) in.readObject();
// 手动恢复父类字段
if (parentState != null && parentState.startsWith("Parent{name='")) {
String name = parentState.substring(14, parentState.length() - 2);
try {
// 使用反射设置父类私有字段
java.lang.reflect.Field nameField = Parent.class.getDeclaredField("name");
nameField.setAccessible(true);
nameField.set(this, name);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
@Override
public String toString() {
return "ChildWithCustomSerialization{age=" + age + ", parent=" + super.toString() + "}";
}
public static void main(String[] args) throws Exception {
ChildWithCustomSerialization child = new ChildWithCustomSerialization(25);
System.out.println("Original: " + child);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(child);
oos.close();
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
ChildWithCustomSerialization deserializedChild = (ChildWithCustomSerialization) ois.readObject();
ois.close();
System.out.println("Deserialized: " + deserializedChild);
}
}
运行上述代码,输出结果为:
Original: ChildWithCustomSerialization{age=25, parent=Parent{name='CustomParent'}}
Deserialized: ChildWithCustomSerialization{age=25, parent=Parent{name='CustomParent'}}
通过自定义序列化方法,父类字段 name 的值得以保留。这种方法虽然复杂,但能确保父类状态的完整性。
基于程序中某个输入、条件或结果的具体问题
在上述示例中,当父类没有无参构造函数时,反序列化会失败。例如,如果将 Parent 类的无参构造函数移除,只保留有参构造函数,那么反序列化时将抛出异常。这是因为 JVM 无法找到合适的构造函数来初始化父类部分。
此外,如果父类是抽象类,它也不能被序列化,因为抽象类不能被实例化。在这种情况下,子类必须实现自己的序列化逻辑,或者父类必须提供一个具体的实现类。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











