跳到主要内容

Java 断言

提示

1.断言的用途:Java 中的断言用于通过测试我们认为正确的代码来检测错误,使用 assert 关键字。 2.启用和禁用断言:默认情况下,断言在运行时被禁用,可以使用 java -ea:argumentsjava -enableassertions:arguments 来启用断言。 3.断言的形式和效果:断言有两种形式,单条件 (assert condition;) 和带表达式的断言 (assert condition : expression;)。断言失败会抛出 AssertionError,并显示详细信息以帮助调试。

Java 中的断言用于通过测试我们认为是正确的代码来检测错误。

使用 assert 关键字进行断言。

其语法为:

assert condition;

这里,condition 是一个布尔表达式,我们假设在程序执行时它是正确的。

启用断言

默认情况下,断言在运行时被禁用并忽略。

要启用断言,我们使用:

java -ea:arguments

或者

java -enableassertions:arguments

当断言被启用且条件为 true 时,程序正常执行。

但如果在启用断言的情况下条件评估为 false,JVM 会抛出一个 AssertionError,程序立即停止。

示例 1:Java 断言

class Main {
public static void main(String args[]) {
String[] weekends = {"Friday", "Saturday", "Sunday"};
assert weekends.length == 2;
System.out.println("一周有 " + weekends.length + " 个周末");
}
}

输出

一周有 3 个周末

我们得到上面的输出是因为这个程序没有编译错误,并且默认情况下断言是被禁用的。

启用断言后,我们得到以下输出:

线程 "main" 中的异常 java.lang.AssertionError

断言语句的另一种形式

assert condition : expression;

在这种形式的断言语句中,一个表达式被传递给 AssertionError 对象的构造函数。如果条件为 false,这个表达式的值将作为错误的详细信息显示。

详细信息用于捕获和传输断言失败的信息,以帮助调试问题。

示例 2:带表达式的 Java 断言示例

class Main {
public static void main(String args[]) {
String[] weekends = {"Friday", "Saturday", "Sunday"};
assert weekends.length == 2 : "一周只有 2 个周末";
System.out.println("一周有 " + weekends.length + " 个周末");
}
}


输出

线程 "main" 中的异常 java.lang.AssertionError: 一周只有 2 个周末

从上面的示例中我们可以看到,表达式被传递给了 AssertionError 对象的构造函数。如果我们的假设是 false 并且启用了断言,就会抛出一个带有适当信息的异常。

这个信息有助于诊断和修复导致断言失败的错误。

针对特定类和包启用断言

如果我们没有为断言命令行开关提供任何参数,

java -ea

这将启用除系统类之外所有类的断言。

我们也可以使用参数为特定类和包启用断言。可以提供给这些命令行开关的参数有: 在类名中启用断言

要为我们的程序 Main 的所有类启用断言,

java -ea Main

要仅启用一个类,

java -ea:AnimalClass Main

这样就只在 Main 程序中的 AnimalClass 中启用了断言。

在包名中启用断言

要仅为 com.animal 包及其子包启用断言,

java -ea:com.animal... Main

在未命名包中启用断言

要在当前工作目录中的未命名包(当我们不使用包声明时)启用断言。

java -ea:... Main

在系统类中启用断言

要在系统类中启用断言,我们使用不同的命令行开关:

java -esa:arguments

或者

java -enablesystemassertions:arguments

这些开关的参数可以是相同的。

禁用断言

要禁用断言,我们使用:

java -da arguments

或者

java -disableassertions arguments

要在系统类中禁用断言,我们使用:

java -dsa:arguments

或者

java -disablesystemassertions:arguments

禁用断言时传递的参数与启用时相同。

断言的优势

  1. 快速高效地检测和纠正错误。
  2. 断言检查仅在开发和测试期间进行。它们在运行时自动从生产代码中移除,因此不会减慢程序的执行速度。
  3. 它有助于去除样板代码,使代码更具可读性。
  4. 重构和优化代码,更有信心地确保其正确运行。

何时使用断言

1. 不可达代码

不可达代码是在我们尝试运行程序时不会执行的代码。使用断言确保不可达代码确实是不可达的。

让我们举个例子。

void unreachableCodeMethod() {
System.out.println("可达代码");
return;
// 不可达代码
System.out.println("不可达代码");
assert false;
}

让我们再举一个没有默认情况的 switch 语句的例子。

switch (dayOfWeek) {
case "Sunday":
System.out.println("今天是星期天!");
break;
case "Monday":
System.out.println("今天是星期一!");
break;
case "Tuesday":
System.out.println("今天是星期二!");
break;
case "Wednesday":
System.out.println("今天是星期三!");
break;
case "Thursday":
System.out.println("今天是星期四!");
break;
case "Friday":
System.out.println("今天是星期五!");
break;
case "Saturday":
System.out.println("今天是星期六!");
break;
}

上面的 switch 语句表明一周的天数只能是以上 7 个值之一。没有默认情况意味着程序员相信这些情况中的一个总是会执行。

然而,可能还有一些尚未考虑的情况,其中假设实际上是错误的。

应该使用断言检查这个假设,确保不会达到默认的 switch 情况。

default:
assert false: dayofWeek + " 是无效的日期";

如果 dayOfWeek 的值不是有效的日子之一,将抛出 AssertionError

2. 记录假设

许多程序员使用注释来记录他们的潜在假设。让我们举个例子。

if (i % 2 == 0) {
...
} else { // 我们知道 (i % 2 == 1)
...
}

改为使用断言。

随着程序的增长,注释可能会过时并且不

同步。然而,我们将被迫更新 assert 语句;否则,它们可能也会因为有效条件而失败。

if (i % 2 == 0) {
...
} else {
assert i % 2 == 1 : i;
...
}

何时不应使用断言

1. 公共方法中的参数检查

公共方法中的参数可能由用户提供。

因此,如果使用断言来检查这些参数,条件可能会失败并导致 AssertionError

与其使用断言,不如让它导致适当的运行时异常并处理这些异常。

2. 评估影响程序操作的表达式

不要在断言条件中调用方法或评估可能影响程序操作的表达式。

我们举一个包含一周中所有日子名称的列表 weekdays 的例子。


ArrayList<String> weekdays = new ArrayList<>(Arrays.asList("Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" ));

ArrayList<String> weekends= new ArrayList<>(Arrays.asList("Sunday", "Saturday" ));

assert weekdays.removeAll(weekends);

这里,我们试图从 weekdays 数组列表中移除元素 SaturdaySunday

如果断言启用,程序运行正常。但是,如果断言被禁用,则不会从列表中移除这些元素。这可能导致程序失败。

相反,将结果分配给一个变量,然后对该变量进行断言。

ArrayList<String> weekdays = new ArrayList<>(Arrays.asList("Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" ));

ArrayList<String> weekends= new ArrayList<>(Arrays.asList("Sunday", "Saturday" ));

boolean weekendsRemoved = weekdays.removeAll(weekends);
assert weekendsRemoved;

通过这种方式,我们可以确保不论断言是否启用,所有的 weekends 都从 weekdays 中移除。因此,它不会影响未来程序操作。