构造块不适合变量类型安全校验,因其无参数上下文、字段未赋值、无法拦截非法输入;可靠校验必须在构造函数中,遵循“先校验、后赋值、再初始化”顺序。

构造块(即实例初始化块)本身不适合也不应该用于变量类型安全校验。它不是校验发生的正确位置,强行使用会导致逻辑错乱、资源泄漏甚至校验失效。真正可靠、符合工程规范的校验必须落在构造函数中,并严格遵循“先校验、后赋值、再初始化”的顺序。
为什么构造块不能做类型校验
构造块在字节码层面会被编译器自动合并到每个构造函数的开头(在调用 super() 或 this() 之后、字段赋值之前),但它没有参数上下文——你无法访问传入的构造参数,只能看到 this 字段,而此时字段尚未赋值,多数为默认值(null、0、false)。试图写 if (this.name == null) 校验,永远为真,毫无意义。
- 构造块不接收参数,无法判断“用户想传什么”,只能看到“当前对象已是什么”
- 它执行时机夹在父类初始化和字段赋值之间,既不能拦截非法输入,也无法阻止半初始化
- 若在构造块中抛异常,父类可能已完成部分初始化,子类字段却未赋值,状态不一致风险极高
正确做法:在构造函数中完成前置校验
校验动作必须作用于形参,且必须在任何字段赋值或辅助对象创建前执行。这是防御性编程的铁律。
- 对字符串参数:用
Objects.requireNonNull(name, "name required")+name.isBlank()双重检查 - 对数值参数:同步校验范围,如
if (age 150) throw new IllegalArgumentException("age out of range") - 对集合类参数:先判空,再考虑是否需防御性拷贝(如
List.copyOf(list)) - 校验代码必须写在
this(...)或super(...)调用之前(对显式构造器)
record 类的特殊处理方式
record 不允许在隐式构造器中写校验逻辑,但支持声明显式构造器。关键在于:校验语句必须放在 this(...) 委托调用的第一行之前。
例如:
public record Product(String name, BigDecimal price) {public Product {
if (name == null || name.trim().isEmpty())
throw new IllegalArgumentException("name must not be blank");
if (price == null || price.compareTo(BigDecimal.ZERO) throw new IllegalArgumentException("price must be non-negative");
}
}
- 显式构造器签名必须与 record 字段完全一致
- 所有校验必须在
this(...)前完成,否则字段已被隐式赋值,失去“赋值前”意义 - final 字段特性决定了校验只有一次机会,漏掉就不可逆
避免常见陷阱
很多看似“能跑通”的写法实则埋雷:
-
在 getter 中补救:如
return Objects.requireNonNullElse(this.name, "default")—— 这不是校验,是妥协;非法状态已存在 -
把校验逻辑塞进字段初始化表达式:如
private final String name = Objects.requireNonNull(n);——n是未声明的变量,编译不过 - 依赖 IDE 自动生成的 setter 校验:构造阶段绕过 setter,非法对象仍可诞生
- 用日志代替异常:静默容忍非法输入,等于放弃守门职责










