有哪些真实的例子来理解断言的关键作用?
当前回答
让我们假设您要编写一个控制核电站的程序。很明显,即使是最微小的错误也可能导致灾难性的结果,因此您的代码必须是无bug的(为了论证,假设JVM是无bug的)。
Java不是一种可验证的语言,这意味着:你不能计算出你的操作结果会是完美的。这样做的主要原因是指针:它们可以指向任何地方,也可以指向任何地方,因此它们不能被计算为这个确切的值,至少在合理的代码范围内不能。对于这个问题,没有办法证明您的代码在整体上是正确的。但你能做的是证明你至少能在bug发生时找到它。
此思想基于契约式设计(Design-by-Contract, DbC)范式:首先定义(具有数学精度)您的方法应该做什么,然后在实际执行期间通过测试来验证这一点。例子:
// Calculates the sum of a (int) + b (int) and returns the result (int).
int sum(int a, int b) {
return a + b;
}
虽然这很明显可以正常工作,但大多数程序员不会看到其中隐藏的bug(提示:Ariane V因为类似的bug而崩溃)。现在DbC定义您必须始终检查函数的输入和输出,以验证它是否正确工作。Java可以通过断言来做到这一点:
// Calculates the sum of a (int) + b (int) and returns the result (int).
int sum(int a, int b) {
assert (Integer.MAX_VALUE - a >= b) : "Value of " + a + " + " + b + " is too large to add.";
final int result = a + b;
assert (result - a == b) : "Sum of " + a + " + " + b + " returned wrong sum " + result;
return result;
}
如果这个函数现在失败了,您会注意到它。你会知道你的代码中有问题,你知道它在哪里,你知道是什么引起的(类似于异常)。更重要的是:当它发生时停止正确执行,以防止任何进一步的代码使用错误的值,并可能对它所控制的任何东西造成损害。
Java异常是一个类似的概念,但它们不能验证所有内容。如果需要更多的检查(以降低执行速度为代价),则需要使用断言。这样做会使代码膨胀,但最终可以在短得惊人的开发时间内交付产品(越早修复bug,成本就越低)。此外,如果代码中有任何错误,您将检测到它。不可能出现漏洞并在以后引起问题。
这仍然不能保证代码没有错误,但它比通常的程序更接近于这一点。
其他回答
让我们假设您要编写一个控制核电站的程序。很明显,即使是最微小的错误也可能导致灾难性的结果,因此您的代码必须是无bug的(为了论证,假设JVM是无bug的)。
Java不是一种可验证的语言,这意味着:你不能计算出你的操作结果会是完美的。这样做的主要原因是指针:它们可以指向任何地方,也可以指向任何地方,因此它们不能被计算为这个确切的值,至少在合理的代码范围内不能。对于这个问题,没有办法证明您的代码在整体上是正确的。但你能做的是证明你至少能在bug发生时找到它。
此思想基于契约式设计(Design-by-Contract, DbC)范式:首先定义(具有数学精度)您的方法应该做什么,然后在实际执行期间通过测试来验证这一点。例子:
// Calculates the sum of a (int) + b (int) and returns the result (int).
int sum(int a, int b) {
return a + b;
}
虽然这很明显可以正常工作,但大多数程序员不会看到其中隐藏的bug(提示:Ariane V因为类似的bug而崩溃)。现在DbC定义您必须始终检查函数的输入和输出,以验证它是否正确工作。Java可以通过断言来做到这一点:
// Calculates the sum of a (int) + b (int) and returns the result (int).
int sum(int a, int b) {
assert (Integer.MAX_VALUE - a >= b) : "Value of " + a + " + " + b + " is too large to add.";
final int result = a + b;
assert (result - a == b) : "Sum of " + a + " + " + b + " returned wrong sum " + result;
return result;
}
如果这个函数现在失败了,您会注意到它。你会知道你的代码中有问题,你知道它在哪里,你知道是什么引起的(类似于异常)。更重要的是:当它发生时停止正确执行,以防止任何进一步的代码使用错误的值,并可能对它所控制的任何东西造成损害。
Java异常是一个类似的概念,但它们不能验证所有内容。如果需要更多的检查(以降低执行速度为代价),则需要使用断言。这样做会使代码膨胀,但最终可以在短得惊人的开发时间内交付产品(越早修复bug,成本就越低)。此外,如果代码中有任何错误,您将检测到它。不可能出现漏洞并在以后引起问题。
这仍然不能保证代码没有错误,但它比通常的程序更接近于这一点。
下面是最常见的用例。假设你正在打开一个枚举值:
switch (fruit) {
case apple:
// do something
break;
case pear:
// do something
break;
case banana:
// do something
break;
}
只要你处理每一个案子,就没问题。但是有一天,有人会把fig添加到你的枚举中,而忘记把它添加到你的switch语句中。这将产生一个难以捕捉的错误,因为直到您离开switch语句后才会感受到其影响。但是如果你像这样写开关,你可以立即捕获它:
switch (fruit) {
case apple:
// do something
break;
case pear:
// do something
break;
case banana:
// do something
break;
default:
assert false : "Missing enum value: " + fruit;
}
什么时候应该使用Assert ?
很多很好的答案解释了assert关键字的作用,但很少回答真正的问题,“在现实生活中什么时候应该使用assert关键字?”答案是:
几乎从来没有
断言,作为一个概念,是很棒的。好的代码有很多if(…)throw…语句(以及它们的亲戚,如对象。requireNonNull和Math.addExact)。然而,某些设计决策极大地限制了assert关键字本身的效用。
assert关键字背后的驱动思想是不成熟的优化,其主要特性是能够轻松地关闭所有检查。事实上,断言检查在默认情况下是关闭的。
然而,在生产中继续执行不变检查是非常重要的。这是因为完美的测试覆盖率是不可能的,所有的产品代码都会有错误,而断言应该有助于诊断和减轻这些错误。
因此,if(…)的使用抛出…应该是首选的,就像检查公共方法的参数值和抛出IllegalArgumentException时需要它一样。
偶尔,人们可能会被诱惑编写一个不变检查,它确实需要很长时间来处理(并且经常被调用,以至于它很重要)。然而,这样的检查将减缓测试,这也是不可取的。这种耗时的检查通常被写成单元测试。然而,出于这个原因,有时使用assert是有意义的。
不要使用assert,因为它比if(…)throw…(我非常痛苦地说,因为我喜欢干净和漂亮)。如果您无法控制自己,并且可以控制应用程序的启动方式,那么可以随意使用断言,但始终在生产环境中启用断言。不可否认,这是我倾向于做的事情。我正在推动一个lombok注释,它将导致assert的行为更像if(…)throw ....请在这里投票。
(咆哮:JVM开发者是一群糟糕的、过早优化的编码员。这就是为什么你会在Java插件和JVM中听到这么多安全问题。他们拒绝在产品代码中包含基本的检查和断言,而我们正在继续为此付出代价。)
这是另一个例子。我写了一个方法来查找两个排序数组中值的中位数。该方法假设数组已经排序。出于性能考虑,它不应该首先对数组排序,甚至不应该检查以确保它们已排序。然而,对未排序的数据调用此方法是一个严重的错误,我们希望在开发阶段尽早发现这些错误。下面是我处理这些看似矛盾的目标的方法:
public static int medianOf(int[] a, int[] b) {
assert assertionOnlyIsSorted(a); // Assertion is order n
assert assertionOnlyIsSorted(b);
... // rest of implementation goes here. Algorithm is order log(n)
}
public static boolean assertionOnlyIsSorted(int[] array) {
for (int i=1; i<array.length; ++i) {
if (array[i] < array[i-1]) {
return false;
}
return true;
}
}
这样,缓慢的测试只在开发阶段执行,在开发阶段,速度没有捕获错误重要。您希望medianOf()方法具有log(n)性能,但“is sorted”测试是o (n)。因此,我将其放在断言中,以限制其在开发阶段的使用,并为其命名,以明确表示它不适合生产。
这样我就两全其美了。在开发过程中,我知道任何不正确地调用这个函数的方法都会被捕获并修复。而且我知道这样做的缓慢测试不会影响生产中的性能。(这也很好地说明了为什么要在生产环境中关闭断言,而在开发环境中启用断言。)
在Java中assert关键字是做什么的?
让我们看看编译后的字节码。
我们将得出结论:
public class Assert {
public static void main(String[] args) {
assert System.currentTimeMillis() == 0L;
}
}
生成几乎完全相同的字节码:
public class Assert {
static final boolean $assertionsDisabled =
!Assert.class.desiredAssertionStatus();
public static void main(String[] args) {
if (!$assertionsDisabled) {
if (System.currentTimeMillis() != 0L) {
throw new AssertionError();
}
}
}
}
其中Assert.class.desiredAssertionStatus()在命令行传递-ea时为true,否则为false。
我们使用System.currentTimeMillis()来确保它不会被优化掉(assert true;所做的那样)。
合成字段生成后,Java只需要在加载时调用Assert.class.desiredAssertionStatus()一次,然后将结果缓存到那里。参见:“静态合成”是什么意思?
我们可以用以下方法验证:
javac Assert.java
javap -c -constants -private -verbose Assert.class
在Oracle JDK 1.8.0_45中,生成了一个合成的静态字段(参见:“静态合成”是什么意思?):
static final boolean $assertionsDisabled;
descriptor: Z
flags: ACC_STATIC, ACC_FINAL, ACC_SYNTHETIC
与静态初始化项一起使用:
0: ldc #6 // class Assert
2: invokevirtual #7 // Method java/lang Class.desiredAssertionStatus:()Z
5: ifne 12
8: iconst_1
9: goto 13
12: iconst_0
13: putstatic #2 // Field $assertionsDisabled:Z
16: return
主要方法是:
0: getstatic #2 // Field $assertionsDisabled:Z
3: ifne 22
6: invokestatic #3 // Method java/lang/System.currentTimeMillis:()J
9: lconst_0
10: lcmp
11: ifeq 22
14: new #4 // class java/lang/AssertionError
17: dup
18: invokespecial #5 // Method java/lang/AssertionError."<init>":()V
21: athrow
22: return
我们的结论是:
assert没有字节码级别的支持:它是Java语言的概念 assert可以用系统属性-Pcom.me很好地模拟。assert=true替换命令行上的-ea,并抛出新的AssertionError()。
推荐文章
- 在流中使用Java 8 foreach循环移动到下一项
- 访问限制:'Application'类型不是API(必需库rt.jar的限制)
- 用Java计算两个日期之间的天数
- 如何配置slf4j-simple
- 在Jar文件中运行类
- 带参数的可运行?
- 我如何得到一个字符串的前n个字符而不检查大小或出界?
- 我可以在Java中设置enum起始值吗?
- Java中的回调函数
- c#和Java中的泛型有什么不同?和模板在c++ ?
- 在Java中,流相对于循环的优势是什么?
- Jersey在未找到InjectionManagerFactory时停止工作
- 在Java流是peek真的只是调试?
- Recyclerview不调用onCreateViewHolder
- 将JSON字符串转换为HashMap