Oph*_*ian 417
在Java 1.4中添加了断言(通过assert关键字).它们用于验证代码中不变量的正确性.它们永远不应该在生产代码中触发,并且表示错误或滥用代码路径.它们可以通过命令-ea上的选项在运行时激活java,但默认情况下不会打开.
一个例子:
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;
}
Run Code Online (Sandbox Code Playgroud)
Two*_*The 320
让我们假设您应该编写一个程序来控制核电站.很明显,即使是最小的错误也可能带来灾难性的结果,因此您的代码必须没有错误(假设JVM在参数方面没有错误).
Java不是一种可验证的语言,这意味着:您无法计算出您的操作结果是否完美.这个的主要原因是指针:它们可以指向任何地方或任何地方,因此它们不能被计算为具有这个精确值,至少不在合理的代码范围内.鉴于此问题,无法证明您的代码在整体上是正确的.但你能做的就是证明你至少在发生错误时找到它们.
这个想法基于契约式设计(DbC)范例:您首先定义(以数学精度)您的方法应该做什么,然后通过在实际执行期间测试它来验证这一点.例:
// Calculates the sum of a (int) + b (int) and returns the result (int).
int sum(int a, int b) {
return a + b;
}
Run Code Online (Sandbox Code Playgroud)
虽然这很明显可以正常工作,但大多数程序员都不会在这个内部看到隐藏的错误(提示:由于类似的错误,Ariane V崩溃了).现在,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;
}
Run Code Online (Sandbox Code Playgroud)
如果此功能现在失败,您会注意到它.您将知道代码中存在问题,您知道它在哪里并且您知道是什么导致它(类似于异常).更重要的是:当它碰巧阻止任何进一步的代码使用错误的值并且可能对其控制的任何内容造成损害时,你就会停止执行.
Java Exceptions是一个类似的概念,但它们无法验证所有内容.如果您想要更多检查(以执行速度为代价),则需要使用断言.这样做会使您的代码膨胀,但最终您可以在极短的开发时间内交付产品(修复错误越早,成本就越低).此外:如果代码中有任何错误,您将检测到它.没有办法让bug滑倒并在以后导致问题.
这仍然不是无错误代码的保证,但它比通常的程序更接近这一点.
Mig*_*noz 62
断言是一种用于捕获代码中的错误的开发阶段工具.它们的设计易于删除,因此它们不会存在于生产代码中.因此,断言不是您向客户提供的"解决方案"的一部分.它们是内部检查,以确保您所做的假设是正确的.最常见的例子是测试null.许多方法都是这样写的:
void doSomething(Widget widget) {
if (widget != null) {
widget.someMethod(); // ...
... // do more stuff with this widget
}
}
Run Code Online (Sandbox Code Playgroud)
通常在这样的方法中,小部件应该永远不会为空.因此,如果它为null,那么您的代码中存在一个需要跟踪的错误.但上面的代码永远不会告诉你这一点.因此,为了编写"安全"代码的善意努力,您也隐藏了一个错误.编写这样的代码要好得多:
/**
* @param Widget widget Should never be null
*/
void doSomething(Widget widget) {
assert widget != null;
widget.someMethod(); // ...
... // do more stuff with this widget
}
Run Code Online (Sandbox Code Playgroud)
这样,您一定会尽早发现这个错误.(在合同中指定此参数永远不应为null也很有用.)确保在开发期间测试代码时打开断言.(说服你的同事这样做也很困难,我觉得很烦人.)
现在,你的一些同事会反对这段代码,认为你仍然应该进行空检查以防止生产中的异常.在这种情况下,断言仍然有用.你可以像这样写:
void doSomething(Widget widget) {
assert widget != null;
if (widget != null) {
widget.someMethod(); // ...
... // do more stuff with this widget
}
}
Run Code Online (Sandbox Code Playgroud)
这样,您的同事会很高兴对生产代码进行空检查,但在开发期间,当窗口小部件为空时,您不再隐藏该错误.
这是一个真实的例子:我曾经写过一个方法,比较两个任意值的相等性,其中任何一个值都可以为null:
/**
* Compare two values using equals(), after checking for null.
* @param thisValue (may be null)
* @param otherValue (may be null)
* @return True if they are both null or if equals() returns true
*/
public static boolean compare(final Object thisValue, final Object otherValue) {
boolean result;
if (thisValue == null) {
result = otherValue == null;
} else {
result = thisValue.equals(otherValue);
}
return result;
}
Run Code Online (Sandbox Code Playgroud)
此代码equals()在thisValue不为null的情况下委托方法的工作.但它假设该equals()方法equals()通过正确处理null参数正确地履行了契约.
一位同事反对我的代码,告诉我许多类都有错误的equals()方法,不测试null,所以我应该检查这个方法.如果这是明智的,或者我们应该强制错误,这是有争议的,所以我们可以发现它并修复它,但我推迟到我的同事并进行空检查,我已经标记了注释:
public static boolean compare(final Object thisValue, final Object otherValue) {
boolean result;
if (thisValue == null) {
result = otherValue == null;
} else {
result = otherValue != null && thisValue.equals(otherValue); // questionable null check
}
return result;
}
Run Code Online (Sandbox Code Playgroud)
other != null只有在equals()方法未能按合同要求检查空值时,才需要在此处进行附加检查.
我没有与我的同事就让错误代码留在我们的代码库中的智慧进行毫无结果的辩论,而是简单地在代码中加入了两个断言.这些断言会让我知道,在开发阶段,如果我们的某个类无法equals()正确实现,那么我可以解决它:
public static boolean compare(final Object thisValue, final Object otherValue) {
boolean result;
if (thisValue == null) {
result = otherValue == null;
assert otherValue == null || otherValue.equals(null) == false;
} else {
result = otherValue != null && thisValue.equals(otherValue);
assert thisValue.equals(null) == false;
}
return result;
}
Run Code Online (Sandbox Code Playgroud)
要记住的重点是:
断言只是开发阶段工具.
断言的目的是让你知道是否存在错误,不只是在你的代码中,而是在你的代码库中.(这里的断言实际上会标记其他类中的错误.)
即使我的同事确信我们的课程写得很好,这里的断言仍然有用.将添加可能无法测试null的新类,并且此方法可以为我们标记这些错误.
在开发过程中,即使您编写的代码不使用断言,也应始终打开断言.我的IDE设置为默认为任何新的可执行文件执行此操作.
断言不会改变生产中代码的行为,所以我的同事很高兴有空检查,并且即使方法有问题,这个方法也会正确执行equals().我很高兴因为我会equals()在开发中捕获任何错误的方法.
此外,您应该通过放入一个失败的临时断言来测试断言策略,这样您就可以确定通过日志文件或输出流中的堆栈跟踪得到通知.
Ale*_*sky 17
很多好的答案解释了assert关键字的作用,但很少回答真正的问题,"什么时候应该assert在现实生活中使用关键字?"
答案:几乎从不.
断言,作为一个概念,是美好的.良好的代码有很多的if (...) throw ...陈述(和他们的亲属象Objects.requireNonNull和Math.addExact).但是,某些设计决策极大地限制了assert 关键字本身的效用.
assert关键字背后的驱动思想是过早优化,主要功能是能够轻松关闭所有检查.实际上,assert默认情况下会关闭检查.
但是,在生产中继续进行不变检查至关重要.这是因为完美的测试覆盖是不可能的,并且所有生产代码都会有错误,断言应该有助于诊断和缓解.
因此,if (...) throw ...应该首选使用,就像检查公共方法的参数值和抛出所需的一样IllegalArgumentException.
偶尔,人们可能会想要编写一个不变的检查,这个检查确实需要花费不必要的长时间来处理(并且通常被称为足够重要).但是,这种检查会减慢测试速度,这也是不合需要的.这种耗时的检查通常写成单元测试.然而,assert出于这个原因,有时使用它可能是有意义的.
不要assert仅仅因为它比它更清洁和更漂亮而使用if (...) throw ...(并且我说它非常痛苦,因为我喜欢干净漂亮).如果您无法自助,并且可以控制应用程序的启动方式,那么请随意使用,assert但始终在生产中启用断言.不可否认,这是我倾向于做的事情.我正在推动一个lombok注释,它将导致assert更像if (...) throw ....在这里投票.
(Rant:JVM开发人员是一群可怕的,过早优化的编码器.这就是你在Java插件和JVM中听到如此多安全问题的原因.他们拒绝在生产代码中包含基本检查和断言,我们继续付出代价.)
Mig*_*noz 13
这是最常见的用例.假设您正在启用枚举值:
switch (fruit) {
case apple:
// do something
break;
case pear:
// do something
break;
case banana:
// do something
break;
}
Run Code Online (Sandbox Code Playgroud)
只要你处理每一个案子,你都没事.但总有一天,有人会在你的枚举中添加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;
}
Run Code Online (Sandbox Code Playgroud)
Don*_*ows 12
断言用于检查后置条件和"永远不会失败"的前置条件.正确的代码永远不会失败一个断言; 当它们触发时,它们应该指出一个错误(希望在一个接近问题实际位置的地方).
断言的示例可以是检查以正确的顺序调用特定方法组(例如,hasNext()在之前调用next()的方法Iterator).
Java中的assert关键字有什么作用?
我们来看看编译后的字节码.
我们将得出结论:
public class Assert {
public static void main(String[] args) {
assert System.currentTimeMillis() == 0L;
}
}
Run Code Online (Sandbox Code Playgroud)
生成几乎完全相同的字节码:
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();
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
其中Assert.class.desiredAssertionStatus()是true当-ea传递在命令行上,否则为假.
我们用System.currentTimeMillis()它来确保它不会被优化掉(assert true;确实).
生成合成字段,以便Java只需要Assert.class.desiredAssertionStatus()在加载时调用一次,然后将结果缓存在那里.另请参阅:"静态合成"的含义是什么?
我们可以用以下方式验证:
javac Assert.java
javap -c -constants -private -verbose Assert.class
Run Code Online (Sandbox Code Playgroud)
使用Oracle JDK 1.8.0_45,生成了一个合成静态字段(另请参阅:"静态合成"的含义是什么?):
static final boolean $assertionsDisabled;
descriptor: Z
flags: ACC_STATIC, ACC_FINAL, ACC_SYNTHETIC
Run Code Online (Sandbox Code Playgroud)
与静态初始化程序一起使用:
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
Run Code Online (Sandbox Code Playgroud)
主要方法是:
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
Run Code Online (Sandbox Code Playgroud)
我们得出结论:
assert:它是Java语言概念assert可以使用系统属性很好地模拟在命令行上-Pcom.me.assert=true替换-ea,以及a throw new AssertionError().断言允许检测代码中的缺陷.您可以打开断言进行测试和调试,同时在程序投入生产时将其关闭.
当你知道它是真的时,为什么断言?只有当一切正常时,才会这样.如果程序存在缺陷,则可能实际上并非如此.在此过程的早期检测到这一点可以让您知道出了什么问题.
一个assert语句包含一个可选的沿着这条语句String的消息.
assert语句的语法有两种形式:
assert boolean_expression;
assert boolean_expression: error_message;
Run Code Online (Sandbox Code Playgroud)
以下是一些基本规则,用于管理应使用断言的位置以及不应使用断言的位置.断言应该用于:
验证私有方法的输入参数.不适用于公共方法.public传递错误参数时,方法应抛出常规异常.
在程序中的任何地方,以确保几乎可以肯定的事实的有效性.
例如,如果您确定它只是1或2,则可以使用如下的断言:
...
if (i == 1) {
...
}
else if (i == 2) {
...
} else {
assert false : "cannot happen. i is " + i;
}
...
Run Code Online (Sandbox Code Playgroud)
断言不应该用于:
验证公共方法的输入参数.由于断言可能并不总是被执行,因此应该使用常规异常机制.
验证用户输入的内容的约束.与上述相同.
不应该用于副作用.
例如,这不是一个正确的用法,因为这里断言用于调用doSomething()方法的副作用.
public boolean doSomething() {
...
}
public void someMethod() {
assert doSomething();
}
Run Code Online (Sandbox Code Playgroud)
唯一可以证明这一点的情况是,当您试图找出代码中是否启用了断言时:
boolean enabled = false;
assert enabled = true;
if (enabled) {
System.out.println("Assertions are enabled");
} else {
System.out.println("Assertions are disabled");
}
Run Code Online (Sandbox Code Playgroud)
一个真实世界的例子,来自Stack-class(来自Java文章中的Assertion)
public int pop() {
// precondition
assert !isEmpty() : "Stack is empty";
return stack[--num];
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
452112 次 |
| 最近记录: |