构造代码块不能替代构造函数校验,因其无参数、无法访问构造函数入参、不能声明throws、在字段默认初始化后执行且只能抛RuntimeException;校验必须在构造函数中于赋值前完成,通过抛出IllegalArgumentException等受检异常风格的运行时异常来拦截非法对象。

构造代码块本身不适合做依赖性校验,也不该用于拦截非法对象——它没有参数、无法访问构造函数传入值,更无法抛出受检异常来中止对象创建。真正承担校验职责的,是构造函数,且必须在赋值前完成。
为什么构造代码块不能替代构造函数校验
构造代码块在每次实例化时执行,但它运行在字段默认初始化之后、构造函数体之前。此时你既拿不到传入参数(如 name、age),也无法决定是否跳过后续流程。它不接收任何输入,纯属“被动执行”,天然不具备校验能力。
- 它无法访问形参,所以写不出
if (name == null)这类判断 - 它不能声明 throws,一旦内部抛出异常,只能是 RuntimeException,调用方难以区分是编程错误还是业务非法
- 它在字段已默认赋值(如 int → 0、Object → null)后才执行,此时校验已错过“前置”窗口
正确做法:在构造函数中完成参数级校验
校验动作必须绑定到构造函数的入参上,并在任何字段赋值或资源分配前执行。这是唯一能可靠拦截非法对象的时机。
- 先检查
if (name == null || name.isBlank()),再执行this.name = name; - 数值类参数同步校验范围:
if (age 150) throw new IllegalArgumentException("age must be between 1 and 150"); - 字符串非空+非空白、邮箱格式、URL合法性等,统一用
Objects.requireNonNull()或Preconditions.checkArgument()
如何让校验结果真正“拦截”非法对象
拦截的关键不是打印日志或设默认值,而是明确拒绝构造——抛出异常并中断初始化流程。
- 避免这种写法:
if (age —— 它掩盖了非法输入,对象仍被创建 - 正确写法:
Objects.requireNonNull(name, "name is required");或自定义异常:throw new IllegalStateException("invalid age: " + age); - 异常一旦抛出,JVM 立即终止该次 new 操作,堆中不会留下半初始化对象
进阶建议:配合工具和规范提升可靠性
单靠手写 if 判断容易遗漏或不一致,应结合语言生态与工程规范:
- DTO 层统一用
@Valid+ Hibernate Validator 注解,在 Controller 入口拦截,不进 service - 领域对象构造函数内使用
Preconditions.checkNotNull()等 Guava 工具,语义清晰、可读性强 - 启用 JEP 513 构造函数验证机制(Java 21+),对 this 泄漏、构造中启动线程等危险模式做静态拦截
- 继承场景下,子类构造函数中对传给
super(...)的参数也要校验,不能假设父类会兜底










