我想知道使用break
语句退出循环而不是满足循环条件是否是“坏习惯”?
我对 Java 和 JVM 没有足够的了解,无法知道如何处理循环,所以我想知道这样做是否忽略了一些关键的东西。
这个问题的重点:是否有特定的性能开销?
好主没有。有时,循环中可能会出现满足总体要求但不满足逻辑循环条件的情况。在这种情况下,break
用于阻止您在循环中毫无意义地循环。
例子
String item;
for(int x = 0; x < 10; x++)
{
// Linear search.
if(array[x].equals("Item I am looking for"))
{
//you've found the item. Let's stop.
item = array[x];
break;
}
}
在这个例子中更有意义的是什么。每次都继续循环到 10,即使在你找到它之后,还是循环直到你找到该项目并停止?或者把它放到现实世界的术语中;当你找到你的钥匙时,你会继续寻找吗?
编辑以回应评论
为什么不设置x
为11
打破循环?这是毫无意义。我们有break
!除非您的代码做出的假设x
肯定比10
以后更大(而且可能不应该),否则您只需使用break
.
为了完整起见进行编辑
肯定还有其他方法可以模拟break
。例如,在循环中为终止条件添加额外的逻辑。说它要么是无意义的循环,要么是使用break
是不公平的。正如所指出的,while 循环通常可以实现类似的功能。例如,按照上面的例子..
while(x < 10 && item == null)
{
if(array[x].equals("Item I am looking for"))
{
item = array[x];
}
x++;
}
使用简单意味着您可以通过循环break
实现此功能。for
这也意味着您不必在终止逻辑中不断添加条件,只要您希望循环表现不同。例如。
for(int x = 0; x < 10; x++)
{
if(array[x].equals("Something that will make me want to cancel"))
{
break;
}
else if(array[x].equals("Something else that will make me want to cancel"))
{
break;
}
else if(array[x].equals("This is what I want"))
{
item = array[x];
}
}
而不是while loop
像这样的终止条件:
while(x < 10 && !array[x].equals("Something that will make me want to cancel") &&
!array[x].equals("Something else that will make me want to cancel"))
使用break
,就像几乎任何其他语言功能一样,在特定的上下文中可能是一种不好的做法,在这种情况下你显然在滥用它。但是一些非常重要的习语没有它就无法编码,或者至少会导致代码的可读性大大降低。在这些情况下,break
是要走的路。
换句话说,不要听任何笼统的、无条件的建议——关于break
或其他任何事情。我不止一次看到代码完全憔悴,只是为了从字面上强制执行“良好实践”。
关于您对性能开销的担忧,绝对没有。在字节码级别,无论如何都没有明确的循环结构:所有的流控制都是根据条件跳转来实现的。
JLS 指定中断是循环的异常终止。然而,仅仅因为它被认为是异常并不意味着它没有在许多不同的代码示例、项目、产品、航天飞机等中使用。JVM 规范没有说明存在或不存在性能损失,尽管它是循环后将继续执行清晰的代码。
但是,代码可读性可能会因奇怪的中断而受到影响。如果您在一个复杂的 if 语句中插入一个中断,该语句被副作用和奇怪的清理代码包围,可能是带有标签的多级中断(或者更糟糕的是,一个接一个地带有一组奇怪的退出条件),它不会任何人都可以轻松阅读。
如果你想通过强制迭代变量在迭代范围之外来打破你的循环,或者通过引入一种不必要的直接退出方式,它的可读性不如break
.
但是,以空的方式循环额外的时间几乎总是不好的做法,因为它需要额外的迭代并且可能不清楚。
在我看来,For
当完成固定数量的迭代并且在每次迭代完成之前它们不会停止时,应该使用循环。在您想提前退出的另一种情况下,我更喜欢使用While
循环。即使你读了这两个小词,它似乎也更合乎逻辑。一些例子:
for (int i=0;i<10;i++) {
System.out.println(i);
}
当我快速阅读这段代码时,我肯定会知道它会打印出 10 行然后继续。
for (int i=0;i<10;i++) {
if (someCondition) break;
System.out.println(i);
}
这个对我来说已经不太清楚了。为什么你会首先声明你将进行 10 次迭代,然后在循环中添加一些额外的条件以更快地停止?
我更喜欢以这种方式编写的前面的示例(即使它有点冗长,但这只是多了 1 行):
int i=0;
while (i<10 && !someCondition) {
System.out.println(i);
i++;
}
阅读此代码的每个人都会立即看到有一个额外的条件可能会提前终止循环。
当然,在非常小的循环中,您总是可以讨论每个程序员都会注意到 break 语句。但我可以从我自己的经验中看出,在更大的循环中,这些中断是可以监督的。(这将我们带到另一个话题,开始将代码分成更小的块)
不,如果达到某些所需条件(例如找到匹配项),则退出循环并不是一个坏习惯。很多时候,您可能想要停止迭代,因为您已经实现了您想要的,并且没有必要进一步迭代。但是,请注意确保您不会意外遗漏某些东西或在不需要时爆发。
如果您打破循环,这也可以增加性能改进,而不是迭代数千条记录,即使循环的目的已经完成(即可能是匹配所需的记录已经完成)。
例子 :
for (int j = 0; j < type.size(); j++) {
if (condition) {
// do stuff after which you want
break; // stop further iteration
}
}
在循环中使用中断是完全合法的,它甚至可能是解决某些问题的唯一方法。
然而,它的坏名声来自于新程序员经常滥用它,导致代码混乱,尤其是在本来可以写在循环条件语句中的条件下使用 break 来停止循环。
这不是坏习惯,但它会使代码的可读性降低。解决此问题的一个有用的重构是将循环移动到一个单独的方法,然后使用 return 语句而不是 break,例如这个(示例来自@Chris 的答案):
String item;
for(int x = 0; x < 10; x++)
{
// Linear search.
if(array[x].equals("Item I am looking for"))
{
//you've found the item. Let's stop.
item = array[x];
break;
}
}
可以重构(使用extract 方法):
public String searchForItem(String itemIamLookingFor)
{
for(int x = 0; x < 10; x++)
{
if(array[x].equals(itemIamLookingFor))
{
return array[x];
}
}
}
当从周围的代码中调用时,可以证明它更具可读性。
如果你开始做这样的事情,那么我会说它开始变得有点奇怪,你最好将它移动到一个单独的方法中,该方法returns
在matchedCondition上产生结果。
boolean matched = false;
for(int i = 0; i < 10; i++) {
for(int j = 0; j < 10; j++) {
if(matchedCondition) {
matched = true;
break;
}
}
if(matched) {
break;
}
}
要详细说明如何清理上述代码,您可以重构,将代码移动到一个函数,returns
而不是使用breaks
. 通常,这可以更好地处理 complex/messy breaks
。
public boolean matches()
for(int i = 0; i < 10; i++) {
for(int j = 0; j < 10; j++) {
if(matchedCondition) {
return true;
}
}
}
return false;
}
但是对于像我下面的例子这样简单的东西。一定要使用break
!
for(int i = 0; i < 10; i++) {
if(wereDoneHere()) { // we're done, break.
break;
}
}
并且改变条件,在上面的例子i
中,和j
的值,你只会让代码真的很难阅读。也可能存在上限(示例中为 10)是变量的情况,因此更难猜测将其设置为什么值以退出循环。您当然可以将i
and设置j
为 Integer.MAX_VALUE,但我认为您可以看到这很快就会变得混乱。:)
有许多常见的情况break
是表达算法的最自然的方式。它们被称为“循环半”结构;范式例子是
while (true) {
item = stream.next();
if (item == EOF)
break;
process(item);
}
如果您不能break
为此使用,则必须重复自己:
item = stream.next();
while (item != EOF) {
process(item);
item = stream.next();
}
同样,对于continue
,有一个常见的模式如下所示:
for (item in list) {
if (ignore_p(item))
continue;
if (trivial_p(item)) {
process_trivial(item);
continue;
}
process_complicated(item);
}
这通常比使用 chained 的替代方案更具可读性else if
,尤其是在process_complicated
不止一个函数调用时。
进一步阅读:循环退出和结构化编程:重新开启辩论
break
并continue
破坏了读者的可读性,尽管它通常很有用。不像“goto”概念那么多,但差不多。
此外,如果你使用一些新的语言,比如 Scala(受 Java 和 Ocaml 等函数式编程语言的启发),你会注意到这一点,break
然后continue
就消失了。
尤其是在函数式编程中,要避免这种代码风格:
总结一下:break
并且continue
在 Java 中被广泛用于命令式风格,但对于任何曾经练习函数式编程的编码人员来说,这可能是……奇怪的。
虽然使用 break 是不错的做法,并且它有很多出色的用途,但它不应该是你所依赖的全部。几乎任何使用中断都可以写入循环条件。当使用真实条件时,代码的可读性要高得多,但在长时间运行或无限循环的情况下,中断非常有意义。如上所示,它们在搜索数据时也很有意义。
不,这不是一个坏习惯。这是最简单有效的方法。
如果您事先知道循环必须停止的位置,那么在 、 或 循环中声明条件可能会提高代码for
的while
可读性`do-while
。
否则,这就是break
.