编程语言从一开始就有 goto 关键字。甚至可以说,goto 是汇编语言里程序控制的起源:“若条件 A 成立,则跳到这里;否则跳到那里”。如果阅读编译器最终生成的汇编代码,你就会发现程序控制流中包含了许多跳转。(Java 编译器会生成自己的 “汇编代码”,但这个代码运行在 Java 虚拟机上,而不是直接运行在 CPU 硬件上。)
    goto 是在源码级别上进行的跳转,这给它带来了坏名声。如果一个程序总是从一个地方跳到另一个地方,难道不应该有其他更好的方式来重组代码,让它的控制流不这么不可控吗?随着 Edsger Dykstra 的著名论文 “Go To Statement Considered Harmful” 的发表,goto 开始失宠了。自那以后,抨击 goto 变成了时尚,提倡废弃这个关键字的人则急着找证据。
    正如很多相似情况下的典型做法,遵守中庸之道是最富有成效的。真正的问题并不在于 goto 本身,而在于滥用 goto。在极少数场景下,goto 实际上是组织控制流最好的方式。
    尽管 goto 是 Java 中的一个保留字,但 Java 中并没有使用它——Java 没有 goto。不过 Java 也有一些类似于跳转的操作,这些操作与 break 和 continue 关键字有关。它们不是跳转,而只是中断循环的一种方式。之所以和goto一起讨论,是因为它们使用了相同的机制:标签。
    标签是以冒号结尾的标识符:
    label1:
    在 Java 中,放置标签的唯一地方是正好在迭代语句之前。“正好”的意思就是,不要在标签和迭代之间插入任何语句。在迭代之前使用标签的唯一原因是,你要在这个迭代里再嵌套一个迭代或一个 switch(很快就会学到它)。这是因为 break 和 continue 通常只会中断当前循环,但和标签一起用时,它们可以中断这个嵌套的循环,直接跳转到标签所在的位置:

    1. label1:
    2. outer-iteration {
    3. inner-iteration {
    4. // ...
    5. break; // [1]
    6. // ...
    7. continue; // [2]
    8. // ...
    9. continue label1; // [3]
    10. // ...
    11. break label1; // [4]
    12. }
    13. }

    [1] 这里的 break 中断内部迭代,回到外部迭代。
    [2] 这里的 continue 中断当前执行,回到内部迭代的开始位置。
    [3] 这里的 continue label1 会同时中断内部迭代以及外部迭代,直接跳到 label1 处,然后它实际上会重新进入外部迭代开始继续执行。
    [4] 这里的 break label1 也会中断所有迭代,跳回到 label1 处,不过它并不会重新进入外部迭代。它实际是完全跳出了两个迭代。
    带标签的 break 和带标签的 continue 也可以用于 for 循环:

    1. // control/LabeledFor.java
    2. // for循环里带标签的break和带标签的continue
    3. public class LabeledFor {
    4. public static void main(String[] args) {
    5. int i = 0;
    6. outer: // 此处不能有语句
    7. for(; true ;) { // 无限循环
    8. inner: // 此处不能有语句
    9. for(; i < 10; i++) {
    10. System.out.println("i = " + i);
    11. if(i == 2) {
    12. System.out.println("continue");
    13. continue;
    14. }
    15. if(i == 3) {
    16. System.out.println("break");
    17. i++; // 否则i不会递增
    18. break;
    19. }
    20. if(i == 7) {
    21. System.out.println("continue outer");
    22. i++; // 否则i不会递增
    23. continue outer;
    24. }
    25. if(i == 8) {
    26. System.out.println("break outer");
    27. break outer;
    28. }
    29. for(int k = 0; k < 5; k++) {
    30. if(k == 3) {
    31. System.out.println("continue inner");
    32. continue inner;
    33. }
    34. }
    35. }
    36. }
    37. // 此处不能有标签
    38. }
    39. }
    40. /* 输出:
    41. i = 0
    42. continue inner
    43. i = 1
    44. continue inner
    45. i = 2
    46. continue
    47. i = 3
    48. break
    49. i = 4
    50. continue inner
    51. i = 5
    52. continue inner
    53. i = 6
    54. continue inner
    55. i = 7
    56. continue outer
    57. i = 8
    58. break outer

    注意 break 中断了 for 循环,而 for 循环在执行到末尾之前,它的递增表达式不会执行。因为 break 导致递增表达式被跳过,所以我们在 i == 3 的分支下直接执行递增运算。i == 7 的分支也是这样,continue outer 语句会跳到外部循环顶部,并且跳过内部循环的递增表达式执行,因此我们在这里也进行了直接递增。
    如果没有 break outer 语句,我们就没有办法从内部循环直接跳出外部循环。这是因为 break 本身只能中断最内层的循环(continue 也是一样)。
    如果要在中断循环的同时退出方法,直接用 return 就可以了。
    下面这个例子展示了 while 循环里的带标签的 break 和带标签的 continue:

    // control/LabeledWhile.java
    // while循环里带标签的break和带标签的continue
    public class LabeledWhile {
      public static void main(String[] args) {
        int i = 0;
        outer:
        while(true) {
          System.out.println("Outer while loop");
          while(true) {
            i++;
            System.out.println("i = " + i);
            if(i == 1) {
              System.out.println("continue");
              continue;
            }
            if(i == 3) {
              System.out.println("continue outer");
              continue outer;
            }
            if(i == 5) {
              System.out.println("break");
              break;
            }
            if(i == 7) {
              System.out.println("break outer");
              break outer;
            }
          }
        }
      }
    }
    /* 输出:
    Outer while loop
    i = 1
    continue
    i = 2
    i = 3
    continue outer
    Outer while loop
    i = 4
    i = 5
    break
    Outer while loop
    i = 6
    i = 7
    break outer
    */
    

    同样的规则也适用于 while。

    1. 普通的 continue 会跳到最内层循环的起始处,并继续执行。
    2. 带标签的 continue 会跳到对应标签的位置,并重新进入这个标签后面的循环。
    3. 普通的 break 会 “跳出循环的底部”,也就是跳出当前循环。
    4. 带标签的 break 会跳出标签所指的循环。

    一定要记住,在 Java 里使用标签的唯一理由就是你用到了嵌套循环,而且你需要使用 break 或 continue 来跳出多层的嵌套。
    带标签的 break 和 continue 是较少使用的试验性功能,在此前的编程语言中几乎没有先例。
    Dijkstra 在他的论文 “Go To Statement Considered Harmful” 中,特别反对使用的是标签,而非 goto。他观察到,在一个程序里随着标签的增多,错误的数量也跟着上升2,并且标签和 goto 也使得程序难以分析。注意 Java 的标签不会有这些问题,因为它被限定了应用场景,不能通过点对点跳转的方式改变程序的控制流程。通过限制一个语言特性的使用,我们反而使其更加有用。

    2请注意,这似乎是一个很难证明的断言,并且很可能是属于“相关-因果关系”认知谬误的一个例子。