有哪些真实的例子来理解断言的关键作用?
当前回答
默认情况下,断言是禁用的。要启用它们,我们必须使用-ea选项运行程序(粒度可以改变)。例如,java -ea AssertionsDemo。
使用断言有两种格式:
简单:如。维护1 = = 2;//这将引发AssertionError。 更好的方法:断言1==2:“不可能..1不等于2”; 这将引发AssertionError,并显示给定的消息,因此更好。虽然实际的语法是断言expr1:expr2,其中expr2可以是返回值的任何表达式,但我更多地使用它只是为了打印消息。
其他回答
断言(通过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;
}
断言是用于捕获代码中的错误的开发阶段工具。它们被设计为易于删除,因此它们不会存在于生产代码中。因此断言不是您交付给客户的“解决方案”的一部分。它们是内部检查,以确保你所做的假设是正确的。最常见的例子是测试是否为空。很多方法都是这样写的:
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
}
这样,您一定能尽早发现这个错误。(在合同中指定这个参数永远不应该为空也是有用的。)在开发过程中测试代码时,一定要打开断言。(说服你的同事这样做通常也很困难,我觉得这很烦人。)
现在,您的一些同事会反对这段代码,认为您仍然应该放入null检查,以防止生产中出现异常。在这种情况下,断言仍然有用。你可以这样写:
void doSomething(Widget widget) {
assert widget != null;
if (widget != null) {
widget.someMethod(); // ...
... // do more stuff with this widget
}
}
这样,您的同事就会高兴地看到产品代码有空检查,但在开发过程中,当小部件为空时,您就不再隐藏错误了。
这里有一个真实的例子:我曾经写过一个方法,比较两个任意值是否相等,其中任何一个值都可以为空:
/**
* 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不为空的情况下委托equals()方法的工作。但它假设equals()方法通过正确处理空参数正确地实现了equals()的契约。
一位同事反对我的代码,告诉我我们的许多类都有不测试null的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时才有必要。
我没有与同事就让有bug的代码留在代码库中是否明智进行毫无结果的争论,而是简单地在代码中放入了两个断言。这些断言会让我知道,在开发阶段,如果我们的一个类不能正确地实现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;
}
需要记住的要点如下:
Assertions are development-phase tools only. The point of an assertion is to let you know if there's a bug, not just in your code, but in your code base. (The assertions here will actually flag bugs in other classes.) Even if my colleague was confident that our classes were properly written, the assertions here would still be useful. New classes will be added that might fail to test for null, and this method can flag those bugs for us. In development, you should always turn assertions on, even if the code you've written doesn't use assertions. My IDE is set to always do this by default for any new executable. The assertions don't change the behavior of the code in production, so my colleague is happy that the null check is there, and that this method will execute properly even if the equals() method is buggy. I'm happy because I will catch any buggy equals() method in development.
此外,您应该通过放入一个将失败的临时断言来测试断言策略,这样您就可以确定通过日志文件或输出流中的堆栈跟踪来通知您。
除了这里提供的所有很棒的答案之外,官方Java SE 7编程指南还提供了关于使用assert的非常简洁的手册;通过几个准确的例子,说明了何时使用断言是一个好主意(重要的是,也是坏主意),以及它与抛出异常有何不同。
Link
什么时候应该使用Assert ?
很多很好的答案解释了assert关键字的作用,但很少回答真正的问题,“在现实生活中什么时候应该使用assert关键字?”答案是:
几乎从来没有
断言,作为一个概念,是很棒的。好的代码有很多if(…)throw…语句(以及它们的亲戚,如对象。requireNonNull和Math.addExact)。然而,某些设计决策极大地限制了assert关键字本身的效用。
assert关键字背后的驱动思想是不成熟的优化,其主要特性是能够轻松地关闭所有检查。事实上,断言检查在默认情况下是关闭的。
然而,在生产中继续执行不变检查是非常重要的。这是因为完美的测试覆盖率是不可能的,所有的产品代码都会有错误,而断言应该有助于诊断和减轻这些错误。
因此,if(…)的使用抛出…应该是首选的,就像检查公共方法的参数值和抛出IllegalArgumentException时需要它一样。
偶尔,人们可能会被诱惑编写一个不变检查,它确实需要很长时间来处理(并且经常被调用,以至于它很重要)。然而,这样的检查将减缓测试,这也是不可取的。这种耗时的检查通常被写成单元测试。然而,出于这个原因,有时使用assert是有意义的。
不要使用assert,因为它比if(…)throw…(我非常痛苦地说,因为我喜欢干净和漂亮)。如果您无法控制自己,并且可以控制应用程序的启动方式,那么可以随意使用断言,但始终在生产环境中启用断言。不可否认,这是我倾向于做的事情。我正在推动一个lombok注释,它将导致assert的行为更像if(…)throw ....请在这里投票。
(咆哮:JVM开发者是一群糟糕的、过早优化的编码员。这就是为什么你会在Java插件和JVM中听到这么多安全问题。他们拒绝在产品代码中包含基本的检查和断言,而我们正在继续为此付出代价。)
我不知道Java中这个特性背后的历史。但是我有一个关于断言可以用来做什么的想法,我认为在这个帖子中没有提到过。
假设你有一个枚举,你打开它:(†)
public enum Veggie {
CAULIFLOWER,
CARROT,
}
// Another class
switch (veggie) {
case CAULIFLOWER: value = 5;
case CARROT: value = 3;
}
†:也许优秀的Java编译器/工具会静态地捕获丢失的枚举值。在这种情况下,想象一下你发现了一些bug并修复了它。然后,您可以使用下面描述的技术在代码中适当地添加回归测试。
这里没有好的默认情况。所以你要确保你覆盖了所有的值。当你添加一个新的enum变量时,你要确保你更新了switch块:
public enum Veggie {
CAULIFLOWER,
CARROT,
TOMATO,
}
但事实证明,当用户在web应用程序中加载特定视图时,这段代码会被调用。尽管存在这个错误,视图仍然可以持续存在:这将是一个bug,但不值得干扰视图的加载(让我们说用户只是看到了一些错误的数字)。所以你只需要记录一个警告:
switch (veggie) {
case CAULIFLOWER: value = 5;
case CARROT: value = 3;
default: nonExhaustiveMatchOnEnum();
// […]
public static void nonExhaustiveMatchOnEnum() {
String errorMessage: "Missing veggie";
logger.warn(errorMessage);
}
但是当你在开发代码时,你确实希望这个错误立即失败——开发者可以在五分钟内修复这个错误,而不像你的web应用程序的用户。
所以你可以添加一个assert false:
public static void nonExhaustiveMatchOnEnum() {
String errorMessage: "Missing veggie";
assert false : errorMessage;
logger.warn(errorMessage);
}
现在,应用程序将在本地崩溃,但只在生产中记录一个警告(假设您在本地使用java -ea,而不是在生产中)。
推荐文章
- 在流中使用Java 8 foreach循环移动到下一项
- 访问限制:'Application'类型不是API(必需库rt.jar的限制)
- 用Java计算两个日期之间的天数
- 如何配置slf4j-simple
- 在Jar文件中运行类
- 带参数的可运行?
- 我如何得到一个字符串的前n个字符而不检查大小或出界?
- 我可以在Java中设置enum起始值吗?
- Java中的回调函数
- c#和Java中的泛型有什么不同?和模板在c++ ?
- 在Java中,流相对于循环的优势是什么?
- Jersey在未找到InjectionManagerFactory时停止工作
- 在Java流是peek真的只是调试?
- Recyclerview不调用onCreateViewHolder
- 将JSON字符串转换为HashMap