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


当前回答

断言是可以被关闭的检查。它们很少被使用。为什么?

它们不能用于检查公共方法参数,因为您无法控制它们。 它们不应该用于简单的检查,如result != null,因为这样的检查非常快,几乎没有什么可以保存。

那么,还剩下什么呢?昂贵的条件检查是真实的。像rb树这样的数据结构的不变量就是一个很好的例子。实际上,在JDK8的ConcurrentHashMap中,对于TreeNodes有一些这样有意义的断言。

您确实不希望在生产环境中启用它们,因为它们很容易控制运行时间。 您可能希望在测试期间打开或关闭它们。 在处理代码时一定要打开它们。


有时候,支票不是很贵,但与此同时,你很确定,它会通过。在我的代码中,有例如,

assert Sets.newHashSet(userIds).size() == userIds.size();

我很确定我刚刚创建的列表有独特的元素,但我想记录并再次检查它。

其他回答

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

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

断言(通过assert关键字)是在Java 1.4中添加的。它们用于验证代码中不变量的正确性。它们永远不应该在生产代码中触发,并且指示了代码路径的错误或误用。它们可以在运行时通过java命令上的-ea选项激活,但默认情况下不会打开。

一个例子:

public Foo acquireFoo(int id) {
  Foo result = null;
  if (id > 50) {
    result = fooService.read(id);
  } else {
    result = new Foo(id);
  }
  assert result != null;

  return result;
}

这是另一个例子。我写了一个方法来查找两个排序数组中值的中位数。该方法假设数组已经排序。出于性能考虑,它不应该首先对数组排序,甚至不应该检查以确保它们已排序。然而,对未排序的数据调用此方法是一个严重的错误,我们希望在开发阶段尽早发现这些错误。下面是我处理这些看似矛盾的目标的方法:

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

Assert是一个关键字。它在JDK 1.4中引入。这是两种类型的断言

非常简单的断言语句 简单的断言语句。

默认情况下,所有assert语句都不会被执行。如果assert语句接收到false,则它将自动引发断言错误。