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

avg*_*tvs 69 java

我正盯着2001年的一些旧代码,并发现了这句话:

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

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

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

这基本上不起作用goto吗?使用它是否是好的做法?这让我感到不安.

Tho*_*mas 105

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

}
Run Code Online (Sandbox Code Playgroud)

您可以xxxfirst或替换second(以打破外部或内部循环),因为当您执行两个循环时,当您按下该break语句时,替换xxxthird将无法编译.

  • @JohnnyCoder不,`先破坏;`将_break_(结束)标记为`first`的循环,即它将继续使用`third`.另一方面,`first first;`将结束当前的`first`迭代并打破'second`并继续使用`first`中的第一个`i`. (12认同)
  • 所以,如果我使用`break first;`,编译器会在`first:`之后立即返回到该行并再次执行这些循环吗? (3认同)
  • @TheTechExpertGuy 好吧,不是真的。它只是在或多或少与“if-else”语句相同的意义上类似于“goto”。Goto 更加“灵活”,就像“去任何地方”一样,例如到循环之前的位置(事实上,这就是旧语言用来实现循环的方式:“如果不满足条件,则(再次)转到循环体的开头”) 。我最近不得不处理它们,相信我:“goto”确实很麻烦,而标记的中断则不然。 (2认同)

Nat*_*hes 13

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

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

  • @Boann:我并不是说在所有情况下goto都是完全邪恶的.它只是指针算法,运算符重载和显式内存管理之类的东西,Java的创建者认为这不值得麻烦.在阅读了看起来超过90%的COBOL程序之后,我并不十分悲伤,他们将它从Java中删除了. (5认同)
  • 对于"不那么可怕",请阅读"不那么有用". (3认同)
  • goto 可能比标签更“有用”,@Boann,但这完全被它们的维护成本所抵消。标签不是 goto 的魔杖,这是一件好事。 (2认同)

par*_*fal 8

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

肆无忌惮地使用go to语句会立即导致很难找到一组有意义的坐标来描述过程进度.

或者换句话说,问题goto在于程序可以在代码块的中间到达,而程序员不需要了解程序状态.标准的面向块的构造被设计为清楚地描绘状态转换,标记break的意图是将程序带到特定的已知状态(在包含标记的块之外).

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

所以,一般来说,我会认为标记break为危险.在我看来,这是一个标志,应该将块转换为一个函数,对封闭范围的访问权限有限.

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


log*_*ray 8

总是可以break用新方法替换.

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

List list1, list2;

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

你可以像这样重写它:

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

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