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


当前回答

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

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)。因此,我将其放在断言中,以限制其在开发阶段的使用,并为其命名,以明确表示它不适合生产。

这样我就两全其美了。在开发过程中,我知道任何不正确地调用这个函数的方法都会被捕获并修复。而且我知道这样做的缓慢测试不会影响生产中的性能。(这也很好地说明了为什么要在生产环境中关闭断言,而在开发环境中启用断言。)

其他回答

断言(通过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;
}

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

下面是最常见的用例。假设你正在打开一个枚举值:

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;
}

除了这里提供的所有很棒的答案之外,官方Java SE 7编程指南还提供了关于使用assert的非常简洁的手册;通过几个准确的例子,说明了何时使用断言是一个好主意(重要的是,也是坏主意),以及它与抛出异常有何不同。

Link

断言主要用于调试应用程序,或者用于替换某些应用程序的异常处理,以检查应用程序的有效性。

断言在运行时工作。这里有一个简单的例子,可以非常简单地解释整个概念——assert关键字在Java中做什么?(WikiAnswers)。