Java中替代goto语句的方法

86

goto关键字在Java中没有对应的替代函数。


10
@harigm问道:“如果Java中有goto语句,你可以举个例子展示你会写什么样的代码吗?我认为这才是更重要的问题所在。” - polygenelubricants
3
@harigm:我赞同polygene的观点,您能否更新一下您的问题,说明为什么您的程序需要使用goto语句? - Everyone
13
人们总是谈论永远不要使用goto,但我认为有一个非常好的现实用例是相当知名和经常使用的。那就是确保在从函数返回之前执行一些代码。通常情况下,这些代码是释放锁或类似的操作,但在我的情况下,我希望能够在返回之前跳转到一个break语句,以便我可以进行必需的强制清理操作。当然,如果程序到处都是goto语句,那将是可怕的,但如果限制在方法体中,只要遵循约定(只跳转到函数末尾,永远不要返回),看起来并不会太糟糕。 - Matt Wolfe
7
你身处于好的公司中。Linus Torvalds 曾激烈地为 goto 语句进行辩护 http://kerneltrap.org/node/553。毫无疑问,这是 Project Coin 中最显著的遗漏。 - Fletch
7
在那个例子中,@MattWolfe尝试使用try-finally完成任务吗? - Andres Riofrio
显示剩余14条评论
12个回答

86

您可以使用带标签的BREAK语句:

search:
    for (i = 0; i < arrayOfInts.length; i++) {
        for (j = 0; j < arrayOfInts[i].length; j++) {
            if (arrayOfInts[i][j] == searchfor) {
                foundIt = true;
                break search;
            }
        }
    }

然而,在设计良好的代码中,你不应该需要使用GOTO功能。


90
这不会让你免受迅猛龙的攻击! - Bobby
26
在设计良好的代码中,为什么不需要使用goto功能? - rogermushroom
14
因为只有汇编语言可以使用它。while循环、for循环、do-while循环、foreach循环、函数调用,这些都是在受控且可预测的情况下使用的GOTO语句。从汇编程序的角度来看,它总是会被转换成GOTO语句,但你不应该需要纯GOTO所提供的功能——与函数和循环/控制语句相比,它唯一的优势在于它让你可以跳转到任何代码的中间位置。而这个“优势”本身就是“意大利面条式代码”的定义。 - user1086498
2
@whatsthebeef 这是 Edsger W. Dijkstra 在 1968 年写的著名信件,解释了为什么他认为 goto 是有害的。链接:http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.92.4846&rep=rep1&type=pdf。 - thomanski
12
xkcd #292是一部漫画,其中提到了“raptors”这个词。 - Peter Mortensen
显示剩余7条评论

43

Java中没有与goto概念直接等价的语法。有一些结构可以让你做一些你可以用传统的goto来做的事情。

  • breakcontinue语句允许你跳出循环或switch语句中的块。
  • 带标签的语句和break <label>允许你跳出任意复合语句并到达给定方法(或初始化程序块)内的任何级别。
  • 如果你给一个循环附上标签,你可以使用continue <label>从内部循环继续进行外部循环的下一次迭代。
  • 抛出和捕获异常允许你(有效地)跳出方法调用的多层级。 (然而,异常相对较昂贵,被认为是一种不好的“普通”控制流方式1。)
  • 当然还有return语句。

以上这些Java结构都不允许你向后或者向当前语句嵌套级别相同的代码点进行跳转。它们都跳出一层或多层嵌套(作用域)并且它们都(除了continue)是向下跳转的。这种限制有助于避免旧的BASIC、FORTRAN和COBOL代码中的goto“意大利面条”式编程综合症2


1- 异常的最昂贵的部分是实际创建异常对象及其堆栈跟踪的过程。如果你真的需要使用异常处理来进行“正常”的控制流,你可以预先分配/重用异常对象,或者创建一个自定义异常类,覆盖fillInStackTrace()方法。缺点是异常的printStackTrace()方法将无法提供有用的信息...如果你需要调用它们。

2- 意大利面条式编程出现后,引发了 结构化编程 方法,您需要限制使用可用的语言结构。它可以应用于 BASICFortranCOBOL,但需要谨慎和纪律。完全摆脱 goto 是一个更为实际的解决方案。如果在语言中保留它,总会有一些小丑会滥用它。


循环结构,如 while(...){} 和 **for(...){}**,允许您重复执行代码块,而无需明确跳转到任意位置。此外,方法调用 myMethod() 允许您执行在其他地方找到的代码,并在完成后返回当前块。所有这些都可以用来替换 goto 的功能,同时防止 goto 允许的常见问题,即无法返回。 - Chris Nava
@Chris - 这是真的,但大多数支持GOTO的语言也有更接近Java循环结构的类比。 - Stephen C
1
@Chris - 经典的FORTRAN有'for'循环,经典的COBOL也有循环结构(虽然我记不清细节了)。只有经典的BASIC没有明确的循环结构... - Stephen C

31

仅供娱乐,这里提供了Java中的GOTO实现。

示例:

   1 public class GotoDemo {
   2     public static void main(String[] args) {
   3         int i = 3;
   4         System.out.println(i);
   5         i = i - 1;
   6         if (i >= 0) {
   7             GotoFactory.getSharedInstance().getGoto().go(4);
   8         }
   9         
  10         try {
  11             System.out.print("Hell");
  12             if (Math.random() > 0) throw new Exception();            
  13             System.out.println("World!");
  14         } catch (Exception e) {
  15             System.out.print("o ");
  16             GotoFactory.getSharedInstance().getGoto().go(13);
  17         }
  18     }
  19 }

运行它:

$ java -cp bin:asm-3.1.jar GotoClassLoader GotoDemo           
   3
   2
   1
   0
   Hello World!
我需要添加“不要使用它!”吗?

2
错误:Math.random() 可能会返回 0。它应该是 >= 0。 - poitroae
是的,任何只能通过字节码黑客技术实现的东西都不算真正的Java... - Stephen C
有没有类似的东西支持标签而不是行号? - Behrouz.M
1
实现的链接已经失效。 - LarsH
1
@poitroae 这是一个有趣的彩蛋,当有人发现输出 HellWorld!o World! 时会感到困惑。#所有的错误都是特性 - ReinstateMonica3167040

18

虽然一些评论者和投票者认为这不是 goto,但下面的Java语句生成的字节码确实表明这些语句确实表达了 goto 语义。

具体来说,第二个示例中的 do {...} while(true); 循环被 Java 编译器优化,以便不评估循环条件。

向前跳转

label: {
  // do stuff
  if (check) break label;
  // do more stuff
}

以字节码形式:

2  iload_1 [check]
3  ifeq 6          // Jumping forward
6  ..

跳转回上一步

label: do {
  // do stuff
  if (check) continue label;
  // do more stuff
  break label;
} while(true);

字节码中:

 2  iload_1 [check]
 3  ifeq 9
 6  goto 2          // Jumping backward
 9  ..

2
@BenHolland:我更新了答案。while(true)被编译器转换为goto字节码操作。由于true是一个常量字面量,编译器可以进行这种优化,而不必评估任何内容。因此,我的示例实际上是一个向后跳转的goto - Lukas Eder
检查字节码(或本地指令)以确定跳转方向前进或后退是无意义的。众所周知,在那个级别存在“goto”原语。跳转指令的方向由编译器选择如何排列代码块来确定,而不是由编程语言级别语义确定的。 - Stephen C
同样,有时你可以编写与使用“goto”语句编写的代码直接等效的代码,但这也是无关紧要的。关于“goto”的问题以及为什么它被认为是“有害的”,在于语义表达的方式……以及对源代码级别可读性的影响。(特别是当滥用“goto”时。) - Stephen C
这确实是最接近的等价物。唯一的区别是它为 do while 创建了一个新的作用域,但我们可以将声明放在它的外面。 - Ciro Santilli OurBigBook.com
哇,我刚学到了一些东西。可以在没有循环的情况下使用break语句。谁能想到呢!这实际上可能会很有用。 - Stefan Reich
显示剩余5条评论

5

如果你真的想要类似于 goto 语句的东西,你总是可以尝试跳转到具有名称的块。

你必须在该块的范围内才能跳转到该标签:

namedBlock: {
  if (j==2) {
    // this will take you to the label above
    break namedBlock;
  }
}

我不会在这里向你讲授为什么应该避免使用goto语句 - 我假设你已经知道了答案。


2
我尝试在我的SO问题中实现这个链接在此。它实际上并没有带你到“上面的标签”,也许我误解了作者的陈述,但为了清晰起见,我会补充说明它带你到外部块(“标记”块)的末尾,这是你要打破的地方下面。因此,您无法向上“打破”。至少这是我的理解。 - NameSpace

4
public class TestLabel {

    enum Label{LABEL1, LABEL2, LABEL3, LABEL4}

    /**
     * @param args
     */
    public static void main(String[] args) {

        Label label = Label.LABEL1;

        while(true) {
            switch(label){
                case LABEL1:
                    print(label);

                case LABEL2:
                    print(label);
                    label = Label.LABEL4;
                    continue;

                case LABEL3:
                    print(label);
                    label = Label.LABEL1;
                    break;

                case LABEL4:
                    print(label);
                    label = Label.LABEL3;
                    continue;
            }
            break;
        }
    }

    public final static void print(Label label){
        System.out.println(label);
    }

如果你运行它足够长的时间,这将导致 StackOverFlowException,因此我需要使用 goto。 - Kevin Parker
3
@Kevin:这会导致堆栈溢出吗?这个算法中并没有递归... - Lukas Eder
1
这看起来非常像原始证明,即任何程序都可以在不使用“goto”的情况下编写 - 通过将其转换为一个带有“标签”变量的while循环,这比任何goto更糟糕十倍。 - gnasher729

3

StephenC写道:

有两个结构可以让你做一些使用经典goto的事情。

还有一个...

Matt Wolfe写道:

人们总是谈论永远不要使用goto,但我认为有一个非常好的现实用例是相当出名和常用的。也就是确保在从函数返回之前执行一些代码。通常是释放锁或其他操作,但在我的情况下,我希望能够在返回之前跳转到break,以便我可以进行必需的强制清理。

try {
    // do stuff
    return result;  // or break, etc.
}
finally {
    // clean up before actually returning, even though the order looks wrong.
}

http://docs.oracle.com/javase/tutorial/essential/exceptions/finally.html

finally块总是在try块退出时执行。这确保了即使出现意外异常,也会执行finally块。但finally不仅用于异常处理 - 它允许程序员避免清理代码被返回、继续或中断绕过的情况。将清理代码放在finally块中始终是一个好习惯,即使不预期发生任何异常也是如此。 与finally相关的一个愚蠢的面试问题是:如果你从try{}块返回,但是在finally{}中也有一个return,那么返回哪个值?

2
我不同意这是一个愚蠢的面试问题。有经验的Java程序员应该知道会发生什么,即使只是为了理解在finally块中放置return是个坏主意的原因。即使这个知识并不是严格必要的,但如果一个程序员没有好奇心去了解,我也会感到担忧... - Stephen C

1
最简单的方法是:


int label = 0;
loop:while(true) {
    switch(state) {
        case 0:
            // Some code
            state = 5;
            break;

        case 2:
            // Some code
            state = 4;
            break;
        ...
        default:
            break loop;
    }
}

0
在现代的Java中,我会使用switch和String。我很惊讶我找不到这个答案:
for (String label = "start";;) {
    switch (label) {
        case "start":
           ...
        case "something":
           ...
        case "whatever":
           ...
           label = "something"; break; // <== those two commands equal goto
           ...
        default:
           return;
    }

}

0

尝试下面的代码。它对我有效。

for (int iTaksa = 1; iTaksa <=8; iTaksa++) { // 'Count 8 Loop is  8 Taksa

    strTaksaStringStar[iCountTaksa] = strTaksaStringCount[iTaksa];

    LabelEndTaksa_Exit : {
        if (iCountTaksa == 1) { //If count is 6 then next it's 2
            iCountTaksa = 2;
            break  LabelEndTaksa_Exit;
        }

        if (iCountTaksa == 2) { //If count is 2 then next it's 3
            iCountTaksa = 3;
            break  LabelEndTaksa_Exit;
        }

        if (iCountTaksa == 3) { //If count is 3 then next it's 4
            iCountTaksa = 4;
            break  LabelEndTaksa_Exit;
        }

        if (iCountTaksa == 4) { //If count is 4 then next it's 7
            iCountTaksa = 7;
            break  LabelEndTaksa_Exit;
        }

        if (iCountTaksa == 7) { //If count is 7 then next it's 5
            iCountTaksa = 5;
            break  LabelEndTaksa_Exit;
        }

        if (iCountTaksa == 5) { //If count is 5 then next it's 8
            iCountTaksa = 8;
            break  LabelEndTaksa_Exit;
        }

        if (iCountTaksa == 8) { //If count is 8 then next it's 6
            iCountTaksa = 6;
            break  LabelEndTaksa_Exit;
        }

        if (iCountTaksa == 6) { //If count is 6 then loop 1  as 1 2 3 4 7 5 8 6  --> 1
            iCountTaksa = 1;
            break  LabelEndTaksa_Exit;
        }
    }   //LabelEndTaksa_Exit : {

} // "for (int iTaksa = 1; iTaksa <=8; iTaksa++) {"

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接