Java断言关键字做什么,什么时候使用?
为了理解断言的关键作用,一些真实的例子是什么?
在Java 1.4中添加了断言 (通过assert关键字)。 它们用于validation代码中不variables的正确性。 它们不应该在生产代码中被触发,并且指示代码path的错误或误用。 它们可以通过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; }
我们假设你应该编写一个程序来控制核电站。 很显然,即使是最小的错误也可能带来灾难性的结果,所以你的代码必须是无 bug的(假设JVM为了论证而没有错误)。
Java不是可validation的语言,这意味着:你不能计算出你的操作的结果是完美的。 其主要原因是指针:它们可以指向任何地方或任何地方,因此不能计算出这个确切的值,至less不在合理的代码范围内。 鉴于这个问题,没有办法certificate你的代码在整体上是正确的。 但是你可以做的是certificate你至less在发生错误的时候find每一个bug。
这个想法是基于契约devise (DbC)的范例:你首先定义(用math的精度)你的方法应该做什么,然后在实际执行过程中通过testing来validation它。 例:
// 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坠毁)。 现在,DbC定义您必须始终检查函数的input和输出以validation它是否正确工作。 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; }
如果这个function现在失败了,你会注意到它。 你会知道你的代码有问题,你知道它在哪里,你知道是什么造成的(类似于Exceptions)。 甚至更重要的是:当发生错误的值时,你不再执行正确的代码,并且可能会造成任何控制的损害。
Javaexception是一个类似的概念,但是它们无法validation一切。 如果你想要更多的检查(以执行速度为代价),你需要使用断言。 这样做会使你的代码膨胀,但是最终你可以在一个惊人的短的开发时间内交付一个产品(越早修复一个bug,成本就越低)。 此外:如果您的代码中有任何错误,您将检测到它。 没有任何方法可能会导致漏洞,并在以后引发问题。
这仍然不是无错代码的保证,但比通常的程序更接近于此。
断言是一个开发阶段的工具来捕获代码中的错误。 它们被devise为易于移除,所以它们不会存在于生产代码中。 因此,断言不是您交付给客户的“解决scheme”的一部分。 他们是内部检查,以确保你所做的假设是正确的。 最常见的例子是testingnull。 许多方法是这样写的:
void doSomething(Widget widget) { if (widget != null) { widget.someMethod(); // ... ... // do more stuff with this widget } }
在像这样的方法中,小部件应该永远不会为空。 所以如果它是空的,你的代码中有一个你需要追踪的错误。 但上面的代码永远不会告诉你这一点。 因此,为了写出“安全”的代码,你也在隐藏一个bug。 编写这样的代码要好得多:
/** * @param Widget widget Should never be null */ void doSomething(Widget widget) { assert widget != null; widget.someMethod(); // ... ... // do more stuff with this widget }
这样,你一定会早点发现这个bug。 (在合约中指定这个参数不应该为null也是有用的。)在开发过程中testing代码时,一定要打开断言。 (说服你的同事也这样做往往是困难的,我觉得很烦人。)
现在,你的一些同事会反对这个代码,争辩说你还应该放入空检查来防止生产中的exception。 在这种情况下,断言仍然有用。 你可以这样写:
void doSomething(Widget widget) { assert widget != null; if (widget != null) { widget.someMethod(); // ... ... // do more stuff with this widget } }
这样一来,你的同事会很高兴的是生产代码有空检查,但在开发过程中,当小部件为空时,你不再隐藏这个bug。
下面是一个真实世界的例子:我曾经写过一个方法,比较两个任意值是否相等,其中任何一个值都可以为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; }
这个代码在thisValue不为null的情况下委托equals()
方法的工作。 但是,它假定equals()
方法通过正确处理null参数正确地实现了equals()
的约定。
一位同事反对我的代码,告诉我,我们的许多类都有不能testing为null的bug equals()
方法,所以我应该把这个检查放到这个方法中。 如果这是明智的话,这是值得商榷的,或者如果我们应该强制这个错误,所以我们可以发现并修复它,但我推迟到我的同事,并在一个空的支票,我已经注明了一个评论:
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; }
这里额外的检查, other != null
,只有当equals()
方法没有按照合同的要求检查null时才是必要的。
我并没有和我的同事进行一场毫无结果的辩论,让我们的代码保留在代码基础上,我只是简单地把两个断言放在代码中。 这些断言让我知道,在开发阶段,如果我们的一个类没有正确实现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; }
要牢记的重点是:
-
断言只是开发阶段的工具。
-
断言的要点是让你知道是否有一个错误,不只是在你的代码,而是在你的代码库 。 (这里的断言实际上会标记其他类中的错误)
-
即使我的同事相信我们的class级写得很好,这里的主张仍然是有用的。 新的类将被添加,可能无法testing为空,这种方法可以标记这些错误给我们。
-
在开发中,即使您编写的代码不使用断言,也应该始终打开断言。 我的IDE设置为默认为任何新的可执行文件。
-
断言不会改变生产中代码的行为,所以我的同事很高兴有空检查,即使
equals()
方法有问题,这个方法也会正确执行。 我很高兴,因为我将在开发中捕获任何错误的equals()
方法。
另外,你应该通过放入一个临时断言来testing你的断言策略,这样你就可以确定通过日志文件或输出stream中的堆栈跟踪来通知你。
很多好的答案解释了assert
关键字的作用,但很less有人回答真正的问题:“什么时候应该在现实生活中使用assert
关键字?”
答案是: 几乎从不 。
断言,作为一个概念,是美好的。 好的代码有很多if (...) throw ...
语句(以及像Objects.requireNonNull
和Math.addExact
)。 但是,某些devise决策大大限制了assert
关键字本身的效用。
assert
关键字背后的驱动思想是不成熟的优化,主要特点是能够轻松closures所有检查。 实际上, assert
检查默认是closures的。
但是,持续不断的生产检查至关重要。 这是因为完美的testing覆盖是不可能的,所有的生产代码都会有错误,这些断言应该有助于诊断和缓解。
因此,使用if (...) throw ...
应该是首选的,就像检查公共方法的参数值和抛出IllegalArgumentException
。
有时,人们可能会试图写一个不确定的检查,而这个检查的确需要很长的时间来处理(而且经常被称为是重要的)。 然而,这样的检查会减慢testing,这也是不希望的。 这种费时的检查通常写成unit testing。 不过,出于这个原因,使用assert
有时候也是有意义的。
不要使用assert
只是因为它比if (...) throw ...
更清洁,更漂亮(我说这是非常痛苦,因为我喜欢干净漂亮)。 如果你只是不能帮助自己,并且可以控制你的应用程序是如何启动的,那么可以随意使用assert
但总是在生产中启用断言。 诚然,这是我倾向于做的。 我正在推动一个lombok注释,这将导致assert
行为更像是if (...) throw ...
在这里投票。
(Rant:JVM开发者是一群糟糕的,过早优化的编码器,这就是为什么你听说Java插件和JVM中有这么多安全问题的原因,他们拒绝在生产代码中包含基本的检查和断言,付出代价)
断言用于检查后置条件和“永远不应该失败”的前提条件。 正确的代码应该永远不会失败断言; 当他们触发时,他们应该指出一个错误(希望在一个接近问题实际位置的地方)。
断言的一个例子可能是检查一组特定的方法是否按照正确的顺序被调用(例如,在Iterator
中hasNext()
之前hasNext()
)。
这是最常见的用例。 假设你打开一个枚举值:
switch (fruit) { case apple: // do something break; case pear: // do something break; case banana: // do something break; }
只要你处理好每一件事情,你都很好。 但有一天,有人会添加无花果到你的枚举,忘记把它添加到您的switch语句。 这会产生一个可能会很棘手的bug,因为在你离开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; }
一个“真实世界的例子”,来自一个Stack类(来自Java文章中的Assertion )
public int pop() { // precondition assert !isEmpty() : "Stack is empty"; return stack[--num]; }
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(); } } } }
当在命令行上传递-ea
时, Assert.class.desiredAssertionStatus()
为true
,否则为false。
我们使用System.currentTimeMillis()
来确保它不会被优化掉( assert true;
did)。
生成合成字段,以便Java只需要在加载时调用Assert.class.desiredAssertionStatus()
一次,然后在那里caching结果。 另请参阅: “静态合成”的含义是什么?
我们可以validation:
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语言的概念 - 系统属性
-Pcom.me.assert=true
可以很好地模拟assert
,在命令行中replace-ea
,并throw new AssertionError()
一个throw new AssertionError()
。
除了这里提供的所有好的答案外,官方的Java SE 7编程指南还有一个关于使用assert
的非常简洁的手册。 用几个现成的例子来说明什么时候使用断言是一个好的(而且,重要的是,坏的)想法,以及它与抛出exception有什么不同。
链接
断言允许检测代码中的缺陷。 您可以打开断言进行testing和debugging,同时在程序生产时closures断言。
当你知道这是真的时,为什么断言? 只有一切正常。 如果程序有缺陷,可能并不是真的。 在此过程中检测到这一点,可以让您知道某些事情是错误的。
一个assert
语句包含这个语句以及一个可选的String
消息。
assert语句的语法有两种forms:
assert boolean_expression; assert boolean_expression: error_message;
这里有一些基本的规则,规定哪里应该使用断言,哪些地方不应该使用断言。 断言应该用于:
-
validation私有方法的input参数。 不适用于公共方法。
public
方法在传递错误参数时应该抛出常规exception。 -
在程序中的任何地方,以确保事实的有效性几乎肯定是正确的。
例如,如果你确定它只能是1或2,你可以使用这样的断言:
... if (i == 1) { ... } else if (i == 2) { ... } else { assert false : "cannot happen. i is " + i; } ...
- validation任何方法结束后的发布条件。 这意味着,在执行业务逻辑之后,可以使用断言来确保variables或结果的内部状态与期望的一致。 例如,打开套接字或文件的方法可以在最后使用断言来确保套接字或文件确实打开。
断言不应该用于:
-
validation公共方法的input参数。 由于断言可能并不总是被执行,所以应该使用常规的exception机制。
-
validation由用户input的内容的约束。 同上。
-
不应该用于副作用。
例如,这不是一个正确的用法,因为这里的断言用于调用doSomething()
方法的副作用。
public boolean doSomething() { ... } public void someMethod() { assert doSomething(); }
唯一可以certificate这一点的情况是当你试图找出你的代码中是否启用了断言:
boolean enabled = false; assert enabled = true; if (enabled) { System.out.println("Assertions are enabled") } else { System.out.println("Assertions are disabled") }
断言在开发时非常有用。 如果代码正常工作,就可以使用它。 它很容易使用,并且可以永远保持在代码中,因为它会在现实生活中被closures。
如果在现实生活中有可能发生这种情况,那么你必须处理它。
我喜欢它,但不知道如何在Eclipse / Android / ADT中打开它。 即使在debugging时,它似乎也是closures的。 (这里有一个线程,但它指的是'Java vm',它不出现在ADT运行configuration中)。
断言基本上用于debugging应用程序,或者用于replace某些应用程序的exception处理,以检查应用程序的有效性。 断言在运行时工作。 一个简单的例子可以解释整个概念很简单 – 维基答案
基本上“断言”将通过“断言”将失败让我们看看这将如何工作
public static void main(String[] args) { String s1 = "Hello"; assert checkInteger(s1); } private static boolean checkInteger(String s) { try{ Integer.parseInt(s); return true; } catch(Exception e) { return false; } }
这里很好的答案。 就像为此添加一点点。 断言在默认情况下是禁用的。 要启用它们,我们必须使用-ea选项运行程序(粒度可以变化)。 例如。 java -ea AssertionsDemo
使用断言有两种格式:1)简单:例如。 assert 1==2; //this will raise an AssertionError
assert 1==2; //this will raise an AssertionError
。 2)更好: assert 1==2: "no way.. 1 is not equal to 2";
这将引发一个AssertionError,同时显示给定的消息。 从而更好。 虽然实际的语法是断言expr1:expr2其中expr2可以是任何expression式返回一个值,我用它更经常只是打印一条消息。 希望这可以帮助。
这是我在一个Hibernate / SQL项目的服务器上写的断言。 一个实体bean有两个有效的布尔属性,叫做isActive和isDefault。 每个可以具有“Y”或“N”的值或空值,其被视为“N”。 我们要确保浏览器客户端限于这三个值。 所以,在这两个属性的引导者中,我添加了这个断言:
assert new HashSet<String>(Arrays.asList("Y", "N", null)).contains(value) : value;
注意以下几点。
-
这个断言只是在开发阶段。 如果客户发送了一个不好的价值,我们会尽早抓住这个问题,在我们达成产品之前就已经修好了。 断言是指可以提早发现的缺陷。
-
这个说法是缓慢而低效的。 没关系。 断言可以自由缓慢。 我们不关心,因为他们是只有开发工具。 这不会减慢生产代码,因为断言将被禁用。 (关于这一点,有一些分歧,我将在后面讨论)。这导致了我的下一个观点。
-
这个说法没有副作用。 我可以用一个不可修改的静态最终集合来testing我的价值,但是这个集合本来会留在生产中,永远不会被使用。
-
这个断言是为了validation客户端的正确操作。 所以当我们达到生产的时候,我们可以肯定客户的运作是正常的,所以我们可以放心地把这个断言转向。
-
有人问:如果生产中不需要断言,为什么不把它们拿出来呢? 因为当你开始下一个版本时,你仍然需要它们。
有些人认为,你绝对不应该使用断言,因为你永远不能确定所有的错误都消失了,所以你甚至需要在生产中保留它们。 所以使用assert语句没有意义,因为断言的唯一好处是可以closures它们。 因此,根据这个想法,你应该(几乎)不要使用断言。 我不同意。 当然,如果一个testing属于生产,你不应该使用断言。 但是这个testing不属于生产。 这是为了捕捉一个不可能达到产量的错误,所以在完成之后可以安全地closures它。
顺便说一下,我可以这样写:
assert value == null || value.equals("Y") || value.equals("N") : value;
这只适用于三个值,但如果可能值的数目变大,则HashSet版本将变得更加方便。 我select了HashSet版本来提高效率。
回顾一下(对许多语言而言,这不仅仅是Java):
在debugging过程中,“assert”主要用作软件开发人员的debugging辅助工具。 断言消息不应该出现。 许多语言提供了一个编译时选项,将导致所有“断言”被忽略,用于生成“生产”代码。
“exception”是一种处理各种错误情况的方便方法,无论它们是否代表逻辑错误,因为如果遇到无法继续的错误条件,则可以简单地“将它们抛到空中, “无论你在哪里,都期待别人在那里准备”抓住“他们。 控制转移一步,直接从抛出exception的代码,直接到捕手的手套。 (捕手可以看到已经发生的呼叫的完整回溯。)
此外,子程序的调用者不必检查子程序是否成功: “如果我们现在在这里,它一定成功了,否则就会抛出一个exception,我们现在不会在这里! 这个简单的策略使代码devise和debugging变得非常简单。
例外情况方便地允许致命错误条件成为“规则的例外”。 而且,对于他们来说,这个代码path也是“规则的例外…… ”飞球!
assert
是一个关键字。 它是在JDK 1.4中引入的。 这是两种types的assert
- 非常简单的
assert
语句 - 简单的
assert
语句。
默认情况下,所有的assert
语句都不会被执行。 如果assert
语句接收到false,则会自动产生断言错误。