面向对象开发如何通过实战在构造函数中实施变量合法性校验?
- 内容介绍
- 相关推荐
本文共计765个文字,预计阅读时间需要4分钟。
构造函数是对象生成的第一道关卡,防御性编程在这里不是锦上添花,而是必须坚守的底线。实践不落俗套,非法参数可能导致对象内部逐渐崩溃,留下空指针、越界、业务逻辑断裂等隐患。
校验对象必须是参数,不是 this 字段
常见错误是写成 if (this.name == null) —— 这时 this.name 还没被赋值,永远为 null,校验形同虚设。真正要检查的是传入的形参 name。
- 正确写法:if (name == null) 或 Objects.requireNonNull(name, "name must not be null")
- 字符串额外加 isBlank() 避免空格占位的“假有效”值
- 数值类参数(如 age、count)需同步校验范围,例如 if (age 150)
校验必须在任何赋值和初始化之前完成
一旦执行了 this.patients = new ArrayList(),再抛异常,对象就处于“半初始化”状态:字段已部分写入,集合已分配但无人管理,资源泄漏风险陡增。
- 严格顺序:参数校验 → 字段赋值 → 辅助对象创建(如集合、缓存、连接)
- 避免在构造函数中做 I/O、网络调用或耗时操作;这类逻辑应移至独立方法或采用构建器/工厂模式
- 若必须初始化外部依赖,确保有对应的清理机制,或改用 lazy 初始化
善用语言与工具提供的校验能力
不要重复造轮子。现代语言生态已提供成熟、语义清晰的校验支持。
- Java 可结合 @NonNull、@Min 等注解 + JEP 513 构造函数验证机制,实现编译期提示与运行时拦截
- Go 中推荐使用 NewXXX() 函数返回 (*T, error) 形式,强制调用方处理失败路径
- C++26 可启用 contracts { require ... } 声明前置条件,兼顾静态分析与运行时断言
- 统一使用 Objects.requireNonNullElse()、Preconditions.checkArgument() 等工具方法,提升可读性与一致性
继承场景下,super() 调用也要受防御约束
子类构造函数中 super() 不只是语法要求,更是状态传递的契约起点。
- super() 必须是第一行,否则编译失败;任何前置日志、校验或计算都得挪到 super() 之后
- 传给 super() 的参数本身也需校验——不能把未清洗的原始参数直接交出去
- 若基类构造函数抛出异常,子类无需 catch,但需确保自身无副作用(如已修改静态状态、已打开文件等)
本文共计765个文字,预计阅读时间需要4分钟。
构造函数是对象生成的第一道关卡,防御性编程在这里不是锦上添花,而是必须坚守的底线。实践不落俗套,非法参数可能导致对象内部逐渐崩溃,留下空指针、越界、业务逻辑断裂等隐患。
校验对象必须是参数,不是 this 字段
常见错误是写成 if (this.name == null) —— 这时 this.name 还没被赋值,永远为 null,校验形同虚设。真正要检查的是传入的形参 name。
- 正确写法:if (name == null) 或 Objects.requireNonNull(name, "name must not be null")
- 字符串额外加 isBlank() 避免空格占位的“假有效”值
- 数值类参数(如 age、count)需同步校验范围,例如 if (age 150)
校验必须在任何赋值和初始化之前完成
一旦执行了 this.patients = new ArrayList(),再抛异常,对象就处于“半初始化”状态:字段已部分写入,集合已分配但无人管理,资源泄漏风险陡增。
- 严格顺序:参数校验 → 字段赋值 → 辅助对象创建(如集合、缓存、连接)
- 避免在构造函数中做 I/O、网络调用或耗时操作;这类逻辑应移至独立方法或采用构建器/工厂模式
- 若必须初始化外部依赖,确保有对应的清理机制,或改用 lazy 初始化
善用语言与工具提供的校验能力
不要重复造轮子。现代语言生态已提供成熟、语义清晰的校验支持。
- Java 可结合 @NonNull、@Min 等注解 + JEP 513 构造函数验证机制,实现编译期提示与运行时拦截
- Go 中推荐使用 NewXXX() 函数返回 (*T, error) 形式,强制调用方处理失败路径
- C++26 可启用 contracts { require ... } 声明前置条件,兼顾静态分析与运行时断言
- 统一使用 Objects.requireNonNullElse()、Preconditions.checkArgument() 等工具方法,提升可读性与一致性
继承场景下,super() 调用也要受防御约束
子类构造函数中 super() 不只是语法要求,更是状态传递的契约起点。
- super() 必须是第一行,否则编译失败;任何前置日志、校验或计算都得挪到 super() 之后
- 传给 super() 的参数本身也需校验——不能把未清洗的原始参数直接交出去
- 若基类构造函数抛出异常,子类无需 catch,但需确保自身无副作用(如已修改静态状态、已打开文件等)

