Java反序列化不执行任何构造方法,而是JVM直接分配内存并按继承链和声明顺序填充非transient字段;实例变量赋值、初始化块、构造器逻辑均被跳过,仅static初始化和readObject/readResolve可控。

Java 反序列化过程中根本不会执行任何构造方法——包括默认构造器、私有构造器甚至静态初始化块。所谓“避免构造方法带来的状态污染”,其实是个常见误解。真正需要关注的,是反序列化直接填充字段时,如何防止字段值不一致、transient 状态丢失、或类结构变更引发的兼容性问题。
反序列化不调用构造器,但字段填充可能破坏逻辑一致性
JVM 在反序列化时跳过所有构造逻辑,直接分配内存并按字节流写入字段值。这意味着:
- 构造器里做的校验(如非空检查、范围限制)完全不生效
- static 块、实例初始化块、this() 调用全部被绕过
- 如果字段类型不可序列化,或被 transient 修饰,其值会是默认值(null / 0 / false)
- 若字段在构造器中被主动赋值(如 private List<String> data = new ArrayList<>();),该初始化也会被忽略——反序列化只认字节流里的内容
确保字段状态正确恢复的关键做法
重点不是“阻止构造器”,而是让字段在反序列化后处于预期状态:
- 显式声明 serialVersionUID:避免因类结构微调(如新增字段、改访问修饰符)导致 InvalidClassException,中断整个恢复流程
- 用 transient 标记不应持久化的字段(如连接、缓存、线程局部变量),再通过 readObject 方法重建它们
- 在 readObject 中补全逻辑依赖:比如重新初始化集合、重设时间戳、校验关键字段是否为 null 并抛出异常
- 避免在字段声明处做“隐式初始化”:例如 private Date createdAt = new Date(); 这种写法在反序列化时会被覆盖为 null(除非字节流里存了值),容易造成 NPE;应统一移到 readObject 中按需构造
对单例等特殊模式要额外防护
如果类是单例,仅靠 private 构造器无法防住反序列化新建对象。必须配合:
立即学习“Java免费学习笔记(深入)”;
- readResolve 方法返回已有实例,覆盖反序列化结果
- 构造器内加运行时校验(如 Holder.INSTANCE != null 抛异常),防御反射攻击(readResolve 不防反射)
- 优先使用枚举实现单例——JVM 规范禁止枚举反序列化重建,天然免疫
更彻底的规避方式:不用原生序列化
如果业务允许,最安全的做法是放弃 java.io.Serializable:
- 用 JSON(Jackson/Gson)、Protobuf、Avro 等类型安全协议替代,它们不执行任意代码,也不绕过构造逻辑
- 对必须用二进制传输的场景,改用 Externalizable 接口,完全掌控读写逻辑,可主动调用构造器或工厂方法
- 远程通信(如 RPC)启用白名单类过滤(如 Dubbo 的 safeSerialization 配置),或启用 JDK 9+ 的 ObjectInputFilter


















