我想知道使用break
语句退出循环而不是满足循环条件是否是一种“不好的做法”?
我对Java和JVM没有足够的洞察力来了解如何处理循环,所以我想知道我是否忽略了一些关键的东西。
这个问题的焦点:是否存在特定的性能开销?
答案 0 :(得分:119)
好领主没有。有时,在满足整体要求的循环中可能会发生某些事情,而不满足逻辑循环条件。在这种情况下,使用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"))
答案 1 :(得分:18)
使用break
,就像几乎任何其他语言功能一样, 可能在特定的上下文中,在您明显滥用它的情况下。但是如果没有它,一些非常重要的习语就无法编码,或者至少会导致代码的可读性低得多。在这些情况下,break
是可行的方法。
换句话说,不要听取关于break
或其他任何内容的任何一揽子,不合格的建议。我已经看到代码完全憔悴只是为了逐字实施“良好实践”。
关于您对性能开销的关注,绝对没有。在字节码级别,无论如何都没有显式循环结构:所有流控制都是根据条件跳转实现的。
答案 2 :(得分:6)
JLS指定中断是循环的异常终止。但是,仅仅因为它被认为是异常并不意味着它没有在许多不同的代码示例,项目,产品,航天飞机等中使用.JVM规范并未说明是否存在性能损失,尽管它是循环后,清除代码执行将继续。
然而,代码可读性会受到奇怪的中断。如果你在一个由副作用和奇怪的清理代码包围的复杂if语句中中断,可能与标签的多级中断(或者更糟糕的是,一个接一个地具有一组奇怪的退出条件),它不会对任何人都很容易阅读。
如果你想通过强制迭代变量超出迭代范围来破坏你的循环,或者通过引入一种不一定直接的退出方式来破坏你的循环,那么它的可读性就低于break
。
然而,以空的方式循环额外的时间几乎总是不好的做法,因为它需要额外的迭代并且可能不清楚。
答案 3 :(得分:5)
在我看来,当完成固定数量的迭代时,应该使用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语句。但是我可以从我自己的经验中看出,在较大的循环中,这些休息可以被监督。 (这会让我们开始另一个主题,开始在较小的块中拆分代码)
答案 4 :(得分:4)
不,如果达到某些所需条件(如找到匹配项),则跳出循环并不是一个坏习惯。很多时候,你可能想要停止迭代,因为你已经实现了你想要的东西,并且没有必要进一步迭代。但是,要小心确保在不需要时不会遗漏某些东西或突然爆发。
如果你打破循环,这也可以增加性能改进,而不是迭代数千条记录,即使循环的目的已经完成(即可能已经完成匹配所需的记录) )。
示例:
for (int j = 0; j < type.size(); j++) {
if (condition) {
// do stuff after which you want
break; // stop further iteration
}
}
答案 5 :(得分:4)
使用break in循环可能是完全合法的,它甚至可以是解决某些问题的唯一方法。
然而,声誉不好的原因是新程序员通常滥用它,导致代码混乱,特别是通过使用break来在可能首先在循环条件语句中写入的条件中停止循环。
答案 6 :(得分:2)
这不是一种不好的做法,但它会降低代码的可读性。解决这个问题的一个有用的重构是将循环移动到一个单独的方法,然后使用return语句而不是break,例如this(示例从@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 method)到此:
public String searchForItem(String itemIamLookingFor)
{
for(int x = 0; x < 10; x++)
{
if(array[x].equals(itemIamLookingFor))
{
return array[x];
}
}
}
从周围代码调用时可以证明更具可读性。
答案 7 :(得分:2)
如果你开始做这样的事情,那么我会说它开始有点奇怪,你最好把它移到一个单独的方法returns
matchCondition的结果。
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
的函数。这通常是更好地处理复杂/凌乱的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
和j
设置为Integer.MAX_VALUE,但我认为你可以看到这开始变得非常混乱。 :)
答案 8 :(得分:2)
有许多常见情况break
是表达算法最自然的方式。它们被称为“循环半”结构;范例的例子是
while (true) {
item = stream.next();
if (item == EOF)
break;
process(item);
}
如果您不能使用break
,则必须重复自己:
item = stream.next();
while (item != EOF) {
process(item);
item = stream.next();
}
It is generally agreed that this is worse.
同样,对于continue
,有一个看起来像这样的常见模式:
for (item in list) {
if (ignore_p(item))
continue;
if (trivial_p(item)) {
process_trivial(item);
continue;
}
process_complicated(item);
}
这通常比链接else if
的替代方案更具可读性,尤其是当process_complicated
不仅仅是一个函数调用时。
进一步阅读:Loop Exits and Structured Programming: Reopening the Debate
答案 9 :(得分:1)
不,这不是一个坏习惯。这是最简单有效的方式。
答案 10 :(得分:1)
虽然使用休息的做法并不坏,但它有许多优秀的用途,它不应该是你所依赖的。几乎任何中断的使用都可以写入循环条件。当使用真实条件时,代码更具可读性,但在长时间运行或无限循环的情况下,中断非常有意义。它们在搜索数据时也很有意义,如上所示。
答案 11 :(得分:1)
如果您事先知道循环必须停止的位置,则可能会提高代码可读性,以便在for
,while
或`do-while
循环中说明条件。
否则,这是break
的确切用例。
答案 12 :(得分:1)
break
和continue
打破了读者的可读性,尽管它通常很有用。
不如“goto”概念,但差不多。
此外,如果您使用Scala等新语言(受Java和Ocaml等函数式编程语言启发),您会注意到break
和continue
已经消失。
特别是在函数式编程中,避免使用这种代码:
Why scala doesn't support break and continue?
总结一下:break
和continue
在Java中被广泛用于命令式样式,但对于任何曾经练习函数式编程的编码器来说,它可能是......很奇怪。