writeObject和readObject是JVM通过反射自动调用的私有钩子方法,签名必须严格为private void writeObject(ObjectOutputStream)和private void readObject(ObjectInputStream),用于在默认序列化前后插入定制逻辑。

Java 序列化机制中,writeObject 和 readObject 并不是接口定义的方法,而是由 JVM 在运行时通过反射自动查找并调用的“约定方法”。它们不需实现任何接口,也不被编译器强制检查,但一旦在类中声明为 private void writeObject(ObjectOutputStream) 和 private void readObject(ObjectInputStream),就会被 ObjectOutputStream 与 ObjectInputStream 在序列化/反序列化过程中主动识别并执行。
writeObject 是如何被 ObjectOutputStream 调用的
当调用 ObjectOutputStream.writeObject(obj) 时,流程进入 writeObject0(obj, false),最终对普通对象(即实现了 Serializable 的类实例)调用 writeOrdinaryObject。该方法会检查目标类是否**自定义了 writeObject 方法**:
- 通过反射获取类的
private Method writeObject,签名必须严格匹配:void writeObject(ObjectOutputStream) - 若存在且可访问(即使 private),JVM 会调用
method.setAccessible(true)绕过访问控制 - 若不存在,则走默认序列化逻辑:逐个写入非
transient、非static字段
注意:该方法必须是 private,否则会被忽略;也不能是 static 或 final;参数类型和异常声明也必须精确匹配,否则视为无效。
readObject 是如何被 ObjectInputStream 调用的
反序列化时,ObjectInputStream.readObject() 最终调用 readOrdinaryObject。此时系统同样使用反射探测目标类是否有自定义 readObject:
- 查找签名完全一致的
private void readObject(ObjectInputStream)方法 - 若找到,同样设为可访问,并传入当前流实例执行
- 若未定义,JVM 按默认方式重建对象(调用无参构造器),再逐个读取字段值并赋值
关键点在于:该方法在对象实例已创建后才被调用(即先 new 实例,再 fill 字段),所以它内部可以安全地操作 this 引用,比如重置时间戳、恢复临时状态等。
为什么能绕过访问限制?底层依赖 JVM 的 Unsafe 与反射权限提升
ObjectOutputStream 和 ObjectInputStream 属于 JDK 核心类库(位于 java.base 模块),它们拥有特殊权限:
- 可通过
ReflectionFactory获取不受限的MethodAccessor - 调用
setAccessible(true)不受SecurityManager(如启用)的常规拦截(JDK 9+ 模块系统下仍保留此能力) - 这种反射调用不经过普通字节码校验,属于 JVM 序列化子系统的“特权路径”
这也解释了为何普通用户代码无法用相同方式调用同类 private 方法——缺少该上下文中的运行时信任链。
常见误用与注意事项
实际开发中容易踩坑的地方包括:
- 方法签名写错一个字母或多了异常声明(如加了
IOException),JVM 就视作不存在,退回到默认逻辑 - 在
writeObject中忘记调用defaultWriteObject(),会导致所有非 transient 字段丢失 - 在
readObject中未调用defaultReadObject(),字段不会自动填充,值为默认(0 / null / false) - 父类有自定义
writeObject,子类未显式调用super.writeObject(out),父类字段可能不被序列化
这些方法本质是“钩子(hook)”,不是替代整个流程,而是插在默认流程前后执行定制逻辑。










