Java中goto语句的替代方法


83

Java中goto关键字的替代功能是什么?

由于Java没有goto。


10
@harigm:如果 goto能在Java中使用,您可以举一个您将编写的代码类型的例子吗?我认为其中存在着更重要的问题。
polygenelubricants 2010年

3
@harigm:我是第二个多基因,能否请您更新问题,以包括为什么您的程序需要goto?
大家

13
人们总是谈论从未使用过goto,但我认为有一个非常好的现实世界用例,该用例是众所周知的和使用过的。也就是说,确保在从函数返回之前执行一些代码。通常,释放它锁定或不锁定,但就我而言,我希望能够在返回之前跳到一个中断,这样我可以执行必需的强制清理。当然,与后藤散落程序的所有的地方将是可怕的,但如果仅限于方法体似乎是坏,只要你遵循的约定不(只跳到功能到底,决不备份)
马特Wolfe

7
你陪伴很好 Linus Torvalds热情地捍卫了goto kerneltrap.org/node/553。毫无疑问,Project Coin是最明显的遗漏。
Fletch

7
在该示例中,@ MattWolfe不会尝试最终完成工作吗?
Andres Riofrio 2012年

Answers:


83

您可以使用带标签的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功能。


88
这不会使您免于猛禽!
鲍比(Bobby)

25
为什么不应该在经过适当设计的代码中使用goto功能?
rogermushroom

13
因为组装是唯一允许使用的组件。while语句,for语句,do-while,foreach,函数调用,所有这些都是可控且可预测的方式使用的GOTO语句。在汇编程序级别上,它将始终是GOTO,但是您不需要纯GOTO所提供的功能-它相对于函数和循环/控制语句的唯一优势是,它使您可以在任何地方的代码中间跳转。而“优势”本身就是“意大利面条代码”的定义。

1
@whatsthebeef这是Edsger W. Dijkstra 1968年著名的一封信,解释了他为什么认为goto有害
thomanski


42

gotoJava中没有直接等效的概念。有几种结构可以让您完成经典型可以做的一些事情goto

  • breakcontinue语句,可以在循环或switch语句跳跃块了。
  • 带标签的语句,break <label>允许您从任意复合语句跳到给定方法(或初始化程序块)内的任何级别。
  • 如果标记了循环语句,则可以continue <label>从内部循环继续进行外部循环的下一次迭代。
  • 抛出和捕获异常使您可以(有效)跳出方法调用的许多级别。(但是,异常的代价相对较高,被认为是进行“常规”控制流程1的一种不好的方法。)
  • 当然有return

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


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

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


while(...){}for(...){}之类的循环结构使您可以重复执行代码块,而无需显式跳转到任意位置。另外,方法调用myMethod()允许您执行在其他地方找到的代码,并在完成后返回当前块。所有这些都可用于替换goto的功能,同时防止goto允许返回的常见失败问题。
克里斯·纳瓦

@Chris-的确如此,但是大多数支持GOTO的语言也具有与Java循环结构更接近的类似物。
斯蒂芬·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!

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


1
我不会使用它,它的额外功能
gmhk 2010年

1
错误:Math.random()可以返回0。应该为> =
poitroae,2012年

好吧。只能由字节码黑客实现的任何东西都不是真正的Java ...
Stephen C

是否有类似的东西支持标签而不是行号?
Behrouz.M,2013年

1
到实现的链接已断开。
LarsH

17

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

做/如何向后跳?它仍然只是突破。计数器示例:label:do {System.out.println(“ Backward”); 继续标签;} while(false); 我认为在您的示例中while(true)是造成向后跳转的原因。
本荷兰

@BenHolland:continue label是倒退吗
Lukas Eder

我仍然不相信。Continue语句跳过for,while或do-while循环的当前迭代。继续跳转到循环主体的末尾,然后对其进行评估,以获取控制循环的布尔表达式。一会儿是向后跳而不是继续。参见docs.oracle.com/javase/tutorial/java/nutsandbolts/branch.html
Ben Holland,

@BenHolland:从技术上来说您是对的。从逻辑的角度来看,如果// do stuff// do more stuff是感兴趣的陈述,则continue label “具有效果”向后跳(while(true)当然是由于陈述)。我不知道这是一个如此精确的问题,但是……
Lukas Eder 2013年

2
@BenHolland:我更新了答案。该while(true)是由编译器为翻译后goto的字节码操作。作为true常量常量,编译器可以执行此优化,而不必评估任何内容。所以,我的例子确实是一个goto,向后跳……
Lukas Eder

5

如果您确实想要诸如goto语句之类的东西,则可以始终尝试破坏命名块。

您必须在代码块的范围内才能打断标签:

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

我不会教您为什么不应该使用goto-我假设您已经知道答案了。


2
我试图在这里链接的我的SO问题中实现这一点。它实际上并没有带您进入“上面的标签”,也许我误解了作者的陈述,但为清楚起见,我将添加到下面的外部代码块(“标记的”代码块)的末尾你在哪里打破。因此,您不能向上“突破”。至少那是我的理解。
NameSpace 2014年

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,因此我需要转到。
凯文·帕克

3
@Kevin:这将如何导致堆栈溢出?该算法没有递归...
Lukas Eder 2012年

1
这看起来很像原始证明,即无需使用“ goto”就可以编写任何程序-通过将其变成带有“ label”变量的while循环,该循环比任何goto都要糟糕十倍。
gnasher729 2014年

3

StephenC写道:

有两种构造可让您执行经典goto可以执行的一些操作。

多一个...

马特·沃尔夫写道:

人们总是谈论从未使用过goto,但是我认为有一个非常好的现实世界用例,它非常众所周知并已被使用。也就是说,确保在从函数返回之前执行一些代码。通常会释放它。锁定或不锁定,但就我而言,我希望能够在返回之前跳到一个中断,这样我可以执行必需的强制清理。

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

try块退出时,finally块始终执行。这样可以确保即使发生意外异常,也可以执行finally块。但是,最后,它不仅对异常处理有用,它还使程序员避免因返回,继续或中断而意外跳过清理代码。将清理代码放在finally块中始终是一个好习惯,即使没有预期例外的情况。

与final相关的愚蠢面试问题是:如果您从try {}块返回,但您的finally {}也有返回,则返回哪个值?


2
我不同意那是一个愚蠢的面试问题。经验丰富的Java程序员应该知道会发生什么,即使只是理解为什么将areturn放在一个finally块中也是一个坏主意。(即使知识不是绝对必要的,我也会因为没有好奇心的程序员而担心...)
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

试试下面的代码。这个对我有用。

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


-1

Java没有goto,因为它使代码非结构化不清晰阅读。但是,您可以使用goto并将其作为文明形式使用break,不会出现任何问题。continue


使用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

使用继续向后跳

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

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


2
您关于使用continue向后跳的示例是错误的。您不能在循环主体之外使用“ continue”,否则会导致编译错误。但是,在循环内部,continue只是简单地跳过条件循环。因此,诸如while(true){continue;}之类的事件将是一个无限循环,而while(true){}也是如此。
本荷兰
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.