
Java 中断言默认禁用,无法可靠保障抽象类或接口实现的契约;应改用运行时异常(如 RuntimeException)在 final 模板方法中主动校验子类行为,确保重复、越界等非法状态被立即捕获并终止错误传播。
java 中断言默认禁用,无法可靠保障抽象类或接口实现的契约;应改用运行时异常(如 runtimeexception)在 `final` 模板方法中主动校验子类行为,确保重复、越界等非法状态被立即捕获并终止错误传播。
在面向对象设计中,抽象类常通过模板方法模式定义不变逻辑(invariant logic),将可变部分延迟至子类实现。例如,Cells 抽象类声明 protected abstract Cell[] _initCells(),同时提供 public final void initCells() 作为统一入口——该方法必须确保所有子类返回的 Cell[] 满足核心契约:无重复单元格、无越界坐标。
但若依赖 assert 进行校验(如 assert !containsDuplicates();),则存在严重缺陷:JVM 默认不启用断言(需显式添加 -ea 参数),且生产环境几乎从不开启。这意味着契约形同虚设,子类可随意返回非法数据而不触发任何检查,导致后续逻辑崩溃或安全漏洞。
✅ 正确做法是:用不可忽略的运行时异常替代断言。initCells() 作为 final 方法,完全掌控执行流程,应在 _initCells() 调用后立即执行防御性校验,并在失败时抛出明确异常:
public abstract class Cells {
private Cell[] cells;
protected abstract Cell[] _initCells();
public final void initCells() {
this.cells = _initCells();
if (containsDuplicates(cells)) {
throw new IllegalStateException("Contract violation: _initCells() returned duplicate cells");
}
if (anyCellIsOutOfBounds(cells)) {
throw new IllegalArgumentException("Contract violation: _initCells() returned out-of-bounds cell");
}
}
// 辅助校验方法(需自行实现)
private boolean containsDuplicates(Cell[] arr) {
Set<cell> seen = new HashSet();
for (Cell c : arr) {
if (!seen.add(c)) return true;
}
return false;
}
private boolean anyCellIsOutOfBounds(Cell[] arr) {
return Arrays.stream(arr)
.anyMatch(cell -> cell.row() = getBoardHeight()
|| cell.col() >= getBoardWidth());
}
protected abstract int getBoardHeight();
protected abstract int getBoardWidth();
}</cell>
⚠️ 注意事项:
-
避免
System.exit():强制进程退出会破坏 JVM 的可控性,尤其在容器、Web 应用或测试环境中极其危险;应让调用方决定如何处理异常。 -
选用恰当异常类型:
IllegalStateException表示对象状态非法(如重复初始化),IllegalArgumentException表示参数/返回值违反约定,语义清晰利于调试。 -
接口场景的等效方案:接口本身无法包含校验逻辑,但可通过组合 + 模板类实现契约约束。例如定义
CellsFactory接口,配合一个ValidatingCells工厂类,在创建实例时执行校验;或使用注解处理器 + 编译期检查(如 Checker Framework)增强安全性。 -
测试驱动保障:为每个子类编写单元测试,显式验证
initCells()在非法输入下是否准确抛出预期异常,形成自动化契约守门员。
总之,契约不是靠文档约定,而是靠代码强制。用 final 方法封装校验逻辑,用不可屏蔽的异常代替断言,才能真正实现“fail-fast”原则,让错误在源头暴露,而非潜伏至系统深处。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











