89

我盯着 2001 年的一些旧代码,发现了这个声明:

   else {
     do {
       int c = XMLDocumentFragmentScannerImpl.this.scanContent();
       if (c == 60) {
         XMLDocumentFragmentScannerImpl.this.fEntityScanner.scanChar();
         XMLDocumentFragmentScannerImpl.this.setScannerState(1);
         break label913;
       }

我以前从未见过这个,并在这里发现了标记的中断:

http://docs.oracle.com/javase/tutorial/java/nutsandbolts/branch.html

这不是本质上的功能goto吗?使用它甚至是一种好习惯吗?这让我很不安。

4

6 回答 6

129

不,它不像 goto,因为您不能“转到”控制流的另一部分。

从您链接的页面:

break 语句终止带标签的语句;它不会将控制流转移到标签。控制流被转移到紧跟在标记(终止)语句之后的语句。

这意味着您只能中断当前正在执行的循环。

考虑这个例子:

first:
for( int i = 0; i < 10; i++) {
  second:
  for(int j = 0; j < 5; j ++ )
  {
    break xxx;
  }
}

third:
for( int a = 0; a < 10; a++) {

}

您可以替换xxxfirstsecond(以中断外部或内部循环),因为两个循环都在执行,当您点击break语句时,但替换xxx不会third编译。

于 2013-02-19T14:56:45.340 回答
16

它没有那么可怕,goto因为它只将控制发送到标记语句的末尾(通常是循环构造)。令人goto不快的是,它是任何地方的任意分支,包括在方法源代码中找到的标签,因此您可以拥有本地循环行为。Java 中的标签中断功能不允许这种疯狂,因为控制只会向前推进。

大约 12 年前我只使用过一次,在我需要打破嵌套循环的情况下,更结构化的替代方案可以在循环中进行更复杂的测试。我不建议经常使用它,但我不会将其标记为自动错误代码气味。

于 2013-02-19T14:59:02.703 回答
10

总是可以break用新方法代替。

考虑检查两个列表中任何公共元素的代码:

List list1, list2;

boolean foundCommonElement = false;
for (Object el : list1) {
    if (list2.contains(el)) {
        foundCommonElement = true;
        break;
    }
}

你可以像这样重写它:

boolean haveCommonElement(List list1, List list2) {
    for (Object el : list1) {
        if (list2.contains(el)) {
            return true;
        }
    }
    return false;
}

当然,要检查两个列表之间的共同元素,最好使用额外内存的list1.retainAll(new HashSet<>(list2))方法来执行此操作,或者对两个列表进行排序,然后在.O(n)O(n)O(n * log n)O(n)

于 2015-05-24T08:25:03.857 回答
8

在阅读此答案的其余部分之前,请阅读Go To Statement Considered Harmful。如果您不想完整阅读它,这是我认为的关键点:

无节制地使用 go to 语句的直接后果是,很难找到一组有意义的坐标来描述过程进度。

或者换种说法,问题goto在于程序可以到达代码块的中间,而程序员当时并不了解程序状态。标准的面向块的结构旨在清楚地描述状态转换,标记break旨在将程序带到特定的已知状态(包含标记块的外部)。

在现实世界的命令式程序中,状态并没有由块边界清楚地描绘,因此标记是否break是一个好主意是值得怀疑的。如果块改变了从块外部可见的状态,并且有多个点可以退出块,则 a 标签break等效于原语goto。唯一的区别是,与其有机会降落在具有不确定状态的块的中间,不如启动一个具有不确定状态的新块。

所以,一般来说,我会认为一个标签break是危险的。在我看来,这表明该块应该被转换为一个函数,对封闭范围的访问是有限的。

然而,这个示例代码显然是解析器生成器的产物(OP 评论说它是 Xerces 源代码)。解析器生成器(或一般的代码生成器)通常会随意使用它们生成的代码,因为它们对状态有完美的了解,而人类不需要理解它。

于 2013-02-19T16:19:57.380 回答
2

至少在我看来,表达意图的一种更简洁的方式可能是将包含循环的代码片段放入一个单独的方法中,然后简单地return从中提取出来。

例如,改变这个:

someLabel:
for (int j = 0; j < 5; j++)
{
    // do something
    if ...
        break someLabel;
}

进入这个:

private void Foo() {
    for (int j = 0; j < 5; j++)
    {
        // do something
        if ...
            return;
    }
}

对于精通其他语言的开发人员来说,这也更惯用,这些语言可能在未来(或未来的你)使用你的代码。

于 2018-12-17T13:26:31.810 回答
1

这不像一个goto语句,你在其中向后跳转流控制。标签仅向您(程序员)显示中断发生的位置。此外,流控制转移到break.

关于使用它,我个人认为它没有什么大用处,因为一旦你开始编写价值数千行的代码,它就变得微不足道了。但同样,这将取决于用例。

于 2013-02-19T14:59:02.607 回答