在Java中使用带标签的休息是一种很好的做法吗?

时间:2013-02-19 14:53:54

标签: java

我正盯着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那样起作用吗?使用它是否是好的做法?这让我感到不安。

6 个答案:

答案 0 :(得分:103)

不,它不像是一个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++) {

}

您可以将xxx替换为firstsecond(以打破外部或内部循环),因为当您点击break语句时正在执行这两个循环,但用xxx替换third将无法编译。

答案 1 :(得分:12)

它并不像goto那么糟糕,因为它只将控制权发送到标记语句的末尾(通常是循环结构)。使goto不可思议的事情是,它是任意位置的任意分支,包括在方法源中找到更高位置的标签,因此您可以拥有自行生成的循环行为。 Java中的标签中断功能不允许这种疯狂,因为控制只是前进。

我大约12年前才使用它一次,在我需要打破嵌套循环的情况下,结构越多的替代方案就会在循环中进行更复杂的测试。我不建议使用它很多,但我不会将其标记为自动坏代码气味。

答案 2 :(得分:8)

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

  

肆无忌惮地使用go to语句会立即导致找到一组有意义的坐标来描述流程进展变得非常困难。

或者重新说一下,goto的问题在于程序可以在代码块的中间到达,而程序员不需要了解程序状态。标准的面向块的构造旨在清楚地描述状态转换,标记的break旨在使程序进入特定的已知状态(在包含标记的块之外)。

在现实世界的命令式程序中,状态未被块边界清楚地描述,因此标记的break是否是一个好主意是值得怀疑的。如果块改变了从块外部可见的状态,并且有多个点退出块,则标记为break的等效于原始goto。唯一的区别在于,不是在具有不确定状态的块中间着陆的可能性,而是启动具有不确定状态的新块。

因此,一般来说,我认为标记为break是危险的。在我看来,这表明该块应该被转换为一个函数,对访问范围的访问权限有限。

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

答案 3 :(得分:8)

始终可以使用新方法替换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)找到共同元素。

答案 4 :(得分:1)

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

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

答案 5 :(得分:0)

至少在我看来,一种表达意图的更简洁的方法可以是将包含循环的代码片段放入一个单独的方法中,并简单地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;
    }
}

对于精通将来可能会与您的代码一起使用的其他语言的开发人员(或future you),这也是惯用的。