在Java中使用带标签的中断是一种好习惯吗?


80

我盯着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吗?使用它甚至是一种好习惯吗?这让我不安。


2
在哪里label913定义?
Nikolay Kuznetsov

1
在这种情况下,大约200行会在相当大的switch语句的末尾出现。
avgvstvs

继续,标签名称将使代码处理该部分代码中带有标签标记的部分
user_CC

5
那么,在这种情况下,问题在于switch语句的大小!
Cyrille Ka

7
这看起来像生成的代码(实际上是解析器)。生成的代码有很多技巧,这些技巧不会在手写代码中使用(主要是因为它们使生成更容易)。
parsifal 2013年

Answers:


118

不,这不像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说法,但将xxxthird将无法编译。


docs.oracle.com/javase/tutorial/java/nutsandbolts/branch.html如果结合使用continue和label将作为goto起作用
user_CC 2013年

@user_CC不是,您仍然无法跳出控制流程。带标记的continue将仅启动带标记的循环的下一个迭代,并且仅在该循环已被执行时才起作用。
托马斯

3
因此,如果使用break first;,编译器会立即返回到该行first:并再次执行这些循环吗?
Ungeheuer

15
@JohnnyCoder不,break first;它将破坏(结束)标记的循环first,即它将继续进行third。另一方面,continue first;将终止当前的迭代firstsecond随之中断,并将继续iin的下一个值first
汤玛斯(Thomas)

15

它并不那么可怕,goto因为它只将控制发送到带标签的语句(通常是循环构造)的末尾。使得goto它与众不同的是,它是一个任意分支,可以遍及任何地方,包括在方法源中的较高位置找到的标签,这样您就可以拥有自行生成的循环行为。Java中的标签破坏功能不允许这种疯狂,因为控制只能向前进行。

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


6
@Boann:我并不是要暗示goto在所有情况下都是邪恶的。Java的创建者认为,这只是指针算术,运算符重载和显式内存管理之类的事情之一,不值得为此烦恼。在阅读了似乎超过90%的COBOL程序之后,我并不感到沮丧,他们将它排除在Java之外。
内森·休斯

1
哦,@ NathanHughes第一次你是对的,goto真是该死。当然,并非在所有情况下都如此,但是,是的,我永远不会错过它。
2013年

2
goto可能比@Boann标签更“有用”,但这完全被其维护成本所抵消。标签不是goto的魔杖,那是一件好事。
DavidS

9

总是有可能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)


我认为这几乎总是正确的方法。goto(可能)有害,因为它可能会给您带来很多地方-完全看不到正在完成什么;创建一个辅助函数可以使下一个人清楚地知道您要做什么以及要在哪里进行,并且可以给您命名该功能的机会。这样更清晰。
ethanbustad

@ethanbustad但这是关于标签的break,不是goto。当然,在两种情况下,此处显示的重构都是一个好主意。但是,标记break并没有那么宽松,并容易使代码混乱goto
underscore_d

这个例子很糟糕,如果中断不起作用,这里的标签就没用了。
Diracnote

8

在阅读本答案的其余部分之前,请阅读认为有害的声明。如果您不想全部阅读,这里是我认为的重点:

毫无限制地使用go to语句会立即导致结果,即很难找到有意义的坐标集来描述过程进度,这非常困难。

或者换句话说,问题goto在于程序可以到达代码块的中间,而程序员在此时不了解程序状态。面向标准块的标准构造旨在清楚地描述状态转换,带标签break的目的是将程序带入特定的已知状态(包含标签的块之外)。

在现实世界中的命令性程序中,状态并没有明确地由块边界来描绘,因此,标记是否break是一个好主意还值得怀疑。如果该块更改了从该块外部可见的状态,并且有多个点要退出该块,则带标签break的等价于图元goto。唯一的区别是,您可以使用不确定状态开始新的块,而不是降落在不确定状态块中间的机会。

因此,总的来说,我认为标记break为危险的。在我看来,这是一个迹象,表明该块应被转换为功能,并且对封闭范围的访问受到限制。

但是,此示例代码显然是解析器生成器的产品(OP评论说它是Xerces源代码)。解析器生成器(或一般而言,代码生成器)通常会对其生成的代码放任自流,因为它们对状态有完全的了解,而人类无需了解状态。


我不同意 break在某些场合非常有用。例如,当您寻找一些可能来自多个订购来源的价值时。您正在一步一步地尝试,找到第一个时,您break。它比的代码更优雅if / else if / else if / else if。(至少减少了行数。)将所有内容移至方法的上下文可能并不总是可行的。另一种情况是,如果您有条件地脱离几个嵌套级别。简而言之,如果您不喜欢break,那么return除了方法的结尾之外,您不喜欢其他任何地方。
OndraŽižka18年

为了上下文;当诸如dowhile不存在之类的事情出现,而每个人都在if(.. ) goto各处使用时,写了“去声明认为有害” 。它旨在鼓励语言设计师增加对诸如do和的支持while。可悲的是,一旦潮流开始滚动,它就会变成一堆炒作,由无知的人推动,将诸如break“仅执行一次”这样的循环放入循环中,并对所有知道的情况(无论goto是干净的还是危害性较小的)抛出异常。
布伦丹

1

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

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


for(int i = 0; i < 1; i++) { do_something(); if(I_want_to_go_backwards) { i = 0; continue; } }
布伦丹

1

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

这也是开发其他语言的精通更地道,与您在未来的代码(或者工作以后你)。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.