有哪些真实的例子来理解断言的关键作用?


当前回答

在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()。

其他回答

一个真实的例子,来自一个stack类(来自Java文章中的断言)

public int pop() {
   // precondition
   assert !isEmpty() : "Stack is empty";
   return stack[--num];
}

Assert在开发时非常有用。当代码正常工作时,某些事情就不能发生。它易于使用,并且可以永远保留在代码中,因为在现实生活中它将被关闭。

如果这种情况在现实生活中有任何可能发生,那么您必须处理它。

我喜欢它,但不知道如何在Eclipse/Android/ADT中打开它。即使在调试时,它也似乎是关闭的。(有一个关于这个的线程,但它指的是“Java虚拟机”,它不会出现在ADT运行配置中)。

断言允许检测代码中的缺陷。您可以打开断言进行测试和调试,而在程序处于生产状态时关闭断言。

既然你知道它是真的,为什么还要坚持呢?只有当一切都正常工作时,这才是正确的。如果程序有一个缺陷,它实际上可能不是真的。在过程早期检测到这一点可以让您知道哪里出了问题。

assert语句包含此语句以及可选的String消息。

assert语句的语法有两种形式:

assert boolean_expression;
assert boolean_expression: error_message;

下面是一些基本规则,它们控制着断言应该在哪里使用,不应该在哪里使用。断言应该用于:

验证私有方法的输入参数。不是公共方法。当传递坏参数时,公共方法应该抛出常规异常。 在程序的任何地方来确保一个事实的有效性,而这个事实几乎肯定是正确的。

例如,如果你确定它只会是1或2,你可以使用这样的断言:

...
if (i == 1)    {
    ...
}
else if (i == 2)    {
    ...
} else {
    assert false : "cannot happen. i is " + i;
}
...

在任何方法结束时验证后置条件。这意味着,在执行业务逻辑之后,您可以使用断言来确保变量或结果的内部状态与您所期望的一致。例如,打开套接字或文件的方法可以在末尾使用断言来确保确实打开了套接字或文件。

断言不应该用于:

验证公共方法的输入参数。由于断言可能并不总是被执行,因此应该使用常规异常机制。 对用户输入的内容验证约束。同上。 不应用于副作用。

例如,这不是一个正确的用法,因为断言在这里被用于调用doSomething()方法的副作用。

public boolean doSomething() {
...    
}
public void someMethod() {       
assert doSomething(); 
}

唯一可以证明这一点的情况是,当你试图找出断言是否在你的代码中启用时:

boolean enabled = false;    
assert enabled = true;    
if (enabled) {
    System.out.println("Assertions are enabled");
} else {
    System.out.println("Assertions are disabled");
}

什么时候应该使用Assert ?

很多很好的答案解释了assert关键字的作用,但很少回答真正的问题,“在现实生活中什么时候应该使用assert关键字?”答案是:

几乎从来没有

断言,作为一个概念,是很棒的。好的代码有很多if(…)throw…语句(以及它们的亲戚,如对象。requireNonNull和Math.addExact)。然而,某些设计决策极大地限制了assert关键字本身的效用。

assert关键字背后的驱动思想是不成熟的优化,其主要特性是能够轻松地关闭所有检查。事实上,断言检查在默认情况下是关闭的。

然而,在生产中继续执行不变检查是非常重要的。这是因为完美的测试覆盖率是不可能的,所有的产品代码都会有错误,而断言应该有助于诊断和减轻这些错误。

因此,if(…)的使用抛出…应该是首选的,就像检查公共方法的参数值和抛出IllegalArgumentException时需要它一样。

偶尔,人们可能会被诱惑编写一个不变检查,它确实需要很长时间来处理(并且经常被调用,以至于它很重要)。然而,这样的检查将减缓测试,这也是不可取的。这种耗时的检查通常被写成单元测试。然而,出于这个原因,有时使用assert是有意义的。

不要使用assert,因为它比if(…)throw…(我非常痛苦地说,因为我喜欢干净和漂亮)。如果您无法控制自己,并且可以控制应用程序的启动方式,那么可以随意使用断言,但始终在生产环境中启用断言。不可否认,这是我倾向于做的事情。我正在推动一个lombok注释,它将导致assert的行为更像if(…)throw ....请在这里投票。

(咆哮:JVM开发者是一群糟糕的、过早优化的编码员。这就是为什么你会在Java插件和JVM中听到这么多安全问题。他们拒绝在产品代码中包含基本的检查和断言,而我们正在继续为此付出代价。)

断言用于检查后置条件和“永不失败”的前提条件。正确的代码应该永远不会使断言失败;当它们触发时,它们应该指出一个错误(希望是在接近问题的实际位置的地方)。

断言的一个例子可能是检查一组特定的方法是否以正确的顺序被调用(例如,在迭代器中hasNext()在next()之前被调用)。