如何通过超类构造函数创建子类实例?Java序列化实现解析
Java序列化通过超类构造创建子类实例的原理拆解
这个问题问到了Java序列化里一个很容易被忽略的细节——为什么反序列化时能通过超类构造函数生成子类实例,咱们先从完整的测试代码入手,再一步步拆解背后的原理。
完整测试代码
先把你提供的代码补全,能直接运行看到现象:
import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; public class Test { public static class A { public final int a; public A() { this.a = 0; System.out.println("父类A的无参构造被调用"); } } public static class B extends A implements Serializable { public final int b; public B() { this.b = 1; System.out.println("子类B的无参构造被调用"); } } public static void main(String[] args) throws Exception { // 序列化过程 ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(new B()); oos.close(); // 反序列化过程 ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bis); B b = (B) ois.readObject(); ois.close(); System.out.println("反序列化得到的实例:a=" + b.a + ", b=" + b.b); } }
运行这段代码,你会看到输出:
父类A的无参构造被调用 子类B的无参构造被调用 父类A的无参构造被调用 反序列化得到的实例:a=0, b=1
注意看:反序列化时只调用了父类A的构造,子类B的构造完全没被触发,但我们却得到了一个完整的B实例——这就是核心疑问点。
背后的核心原理
Java反序列化的对象创建逻辑和平时new对象完全不同,关键在于这两点:
1. 子类实例:绕过构造函数直接创建
反序列化时,ObjectInputStream会使用底层的非构造方法实例化机制(比如sun.misc.Unsafe.allocateInstance)直接在堆上分配子类对象的内存,完全不调用子类的任何构造函数。这是它能在不执行子类构造的情况下生成实例的核心原因。
2. 未序列化超类:必须用构造初始化
对于没有实现Serializable接口的超类(比如例子里的A),它的成员变量并没有被序列化到字节流中(只有实现Serializable的类的成员才会被序列化)。为了保证超类部分的状态是合法的,JVM会调用这个超类的无参构造函数来初始化它的成员——这就是为什么你会看到反序列化时父类构造被调用的原因。
如果超类没有提供无参构造,反序列化会直接抛出java.io.InvalidClassException,因为JVM没办法初始化超类的状态。
额外的细节补充
- 如果把父类A也实现
Serializable,那么反序列化时连父类的构造也不会被调用,所有成员变量的状态都会直接从字节流中恢复; - 对于
final成员变量:如果是在未序列化超类的构造里赋值(比如A的a),反序列化时会通过构造重新赋值;而子类的final变量(比如B的b)因为被序列化了,会直接从字节流读取值,不会受构造函数影响。
内容的提问来源于stack exchange,提问作者lexicore
相关产品推荐
相关产品推荐

