【问题标题】:Alternative to a goto statement in Java替代 Java 中的 goto 语句
【发布时间】:2011-01-26 17:03:16
【问题描述】:

Java 中 goto 关键字的替代函数是什么?

因为 Java 没有 goto。

【问题讨论】:

  • @harigm:你能举一个例子说明你会写的那种代码如果 goto在Java中可用吗?我认为这是更重要的问题。
  • @harigm:我是第二个 polygene,您能否更新您的问题,包括为什么您的程序需要 goto?
  • 人们总是说从不使用 goto,但我认为有一个非常好的现实世界用例,它是众所周知和使用的。也就是说,确保在返回之前执行一些代码从一个函数..通常它释放锁或不释放锁,但在我的情况下,我希望能够在返回之前跳到一个休息点,这样我就可以进行必要的强制清理。当然,一个到处都是 goto 的程序会很糟糕,但如果仅限于方法体,只要你遵循约定,它似乎并没有那么糟糕(只跳转到函数的末尾,从不备份)跨度>
  • 你们相处得很好。 Linus Torvalds 热情地为 goto kerneltrap.org/node/553 辩护。无疑是 Project Coin 中最显着的遗漏。
  • @MattWolfe 没有尝试-最终完成该示例中的工作?

标签: java keyword goto


【解决方案1】:

虽然一些评论者和反对者认为这不是 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  ..

【讨论】:

  • do/while 如何向后跳转?它仍然只是突破了障碍。反例:label: do {System.out.println("Backward");继续标签;} 而(假);我认为在您的示例中,while(true) 是导致向后跳跃的原因。
  • @BenHolland: continue label 是向后跳跃
  • 我仍然不相信。 continue 语句跳过 for、while 或 do-while 循环的当前迭代。 continue 跳到循环体的末尾,然后对控制循环的布尔表达式求值。 while 是向后跳跃而不是继续。见docs.oracle.com/javase/tutorial/java/nutsandbolts/branch.html
  • @BenHolland:技术上你是对的。从逻辑的角度来看,如果// do stuff// do more stuff 是感兴趣的语句,那么continue label “具有向后跳跃的效果”(当然是由于while(true) 语句)。虽然我不知道这是一个如此精确的问题......
  • @BenHolland:我更新了答案。 while(true) 被编译器翻译成goto 字节码操作。由于true 是一个常量字面量,编译器可以进行这种优化,而无需评估任何内容。所以,我的例子真的是一个 goto,向后跳......
【解决方案2】:

最简单的是:

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;
    }
}

【讨论】:

    【解决方案3】:

    Java 没有goto,因为它使代码unstructuredunclear 可读。但是,您可以将breakcontinue 用作goto文明 形式,而不会出现问题。


    使用 break 向前跳跃 -

    ahead: {
        System.out.println("Before break");
        break ahead;
        System.out.println("After Break"); // This won't execute
    }
    // After a line break ahead, the code flow starts from here, after the ahead block
    System.out.println("After ahead");
    

    输出

    Before Break
    After ahead
    

    使用 continue 向后跳转

    before: {
        System.out.println("Continue");
        continue before;
    }
    

    这将导致无限循环,因为每次执行continue before 行时,代码流将从before 重新开始

    【讨论】:

    • 您使用 continue 向后跳转的示例是错误的。您不能在循环体之外使用“继续”,它会导致编译错误。然而,在循环内部, continue 只是有条件地跳转循环。所以像 while(true){continue;} 这样的东西会是一个无限循环,但 while(true){} 也是如此。
    【解决方案4】:

    使用带标签的中断作为 goto 的替代方法。

    【讨论】:

    • 这个答案是,而且一直是多余的
    【解决方案5】:
    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,因此我需要转到。
    • @Kevin:这将如何导致堆栈溢出?这个算法没有递归...
    • 这看起来很像原始证明,任何程序都可以不使用“goto”来编写——通过将它变成一个带有比任何 goto 差十倍的“label”变量的 while 循环。
    【解决方案6】:

    试试下面的代码。它对我有用。

    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++) {"
    

    【讨论】:

      【解决方案7】:

      Java 中没有任何与goto 概念直接等效的方法。有一些结构可以让您执行一些经典goto 可以执行的操作。

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

      这些 Java 构造都不允许您向后分支或跳转到代码中与当前语句处于同一嵌套级别的某个点。它们都跳出一个或多个嵌套(范围)级别,并且它们都(除了continue)向下跳。此限制有助于避免旧 BASIC、FORTRAN 和 COBOL 代码中固有的 goto“意大利面条代码”综合症2


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

      2 - 意大利面条代码综合症催生了structured programming 方法,在这种方法中,您限制了对可用语言结构的使用。这可以应用于BASICFortranCOBOL,但需要谨慎和自律。完全摆脱goto 是一个更实用的解决方案。如果你把它保留在一种语言中,总会有一些小丑会滥用它。

      【讨论】:

      • while(...){}for(...){} 这样的循环结构允许您重复代码块而无需显式跳转到任意位置。此外,方法调用 myMethod() 允许您执行在其他地方找到的代码,并在完成后返回当前块。所有这些都可以用来替换 goto 的功能,同时防止 goto 允许的无法返回的常见问题。
      • @Chris - 没错,但大多数支持 GOTO 的语言也有更接近 Java 循环结构的类似物。
      • @Chris - 经典的 FORTRAN 有“for”循环,经典的 COBOL 也有循环结构(虽然我不记得细节了)。只有经典的 BASIC 没有明确的循环结构......
      【解决方案8】:

      您可以使用标记为 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 功能。

      【讨论】:

      • 这不会把你从猛龙队中拯救出来!
      • 为什么在正确设计的代码中不需要 goto 功能?
      • 因为汇编是唯一允许使用它的。 while 语句、for 语句、do-while、foreach、函数调用,所有这些都是以受控和可预测方式使用的 GOTO 语句。在汇编器级别上,它始终是 GOTO,但您不应该需要纯 GOTO 为您提供的功能 - 它与函数和循环/控制语句相比的唯一优势是它允许您在任何代码的中间跳转.而这个“优势”本身就是“意大利面条代码”的定义。
      • @whatsthebeef 这是 Edsger W. Dijkstra 1968 年著名的一封信,它解释了他为什么considered goto harmful
      【解决方案9】:

      StephenC 写道:

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

      还有一个……

      马特·沃尔夫写道:

      人们总是说从不使用 goto,但我认为有一个 非常好的现实世界用例,这是众所周知和使用的.. 也就是说,确保在从 a 返回之前执行一些代码 功能..通常它释放锁或什么不,但在我的情况下,我会 喜欢能够在返回前跳到休息,所以我可以做 需要强制清理。

      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{} 中也有返回,那么返回哪个值?

      【讨论】:

      • 我不同意这是一个愚蠢的面试问题。有经验的 Java 程序员应该知道会发生什么,如果只是为了理解为什么return 放入finally 块是一个坏主意。 (即使这些知识不是绝对必要的,我也会担心一个没有好奇心去发现的程序员......)
      【解决方案10】:

      如果你真的想要 goto 语句之类的东西,你总是可以尝试打破命名块。

      你必须在块的范围内才能打破标签:

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

      我不会教你为什么要避免使用 goto - 我假设你已经知道答案了。

      【讨论】:

      • 我尝试在我的 SO 问题 linked here 中实现这一点。它实际上并没有将您带到“上面的标签”,也许我误解了作者的陈述,但为了清楚起见,我会补充说它会将您带到外部块(“标记”块)的末尾,它在下面你在哪里打破。因此,您不能向上“突破”。至少这是我的理解。
      【解决方案11】:

      只是为了好玩,here 是 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!
      

      我需要添加“不要使用它!”吗?

      【讨论】:

      • Bug:Math.random() 可以返回 0。应该是 >=
      • 好的。任何只能由字节码黑客实现的东西都不是真正的 Java ...
      • 是否有类似的东西支持标签而不是行号?
      • 实现的链接已损坏。
      • @poitroae 这是一个有趣的复活节彩蛋,有人会发现HellWorld!o World! 输出并感到困惑。 #AllBugsAreFeatures
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-11-18
      • 1970-01-01
      • 2011-10-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多