臭名昭著的“goto”

在编程语言中很早就有 goto 关键字了。事实上,goto 起源于汇编语言的程序控制:“若条件
A 成立,则跳到这里;否则跳到那里”。如果阅读由编译器生成的最终的汇编代码,就会发
现程序控制里包含了许多跳转。(Java 编译器生成它自己的“汇编代码”,但是这个代码是运
行在 Java 虚拟机上的,而不是直接运行在 CPU 硬件上。)


goto 语句是在源码级上的跳转,这使其招致了不好的声誉。若一个程序总是从一个地方跳
到另一个地方,还有什么办法能识别程序的控制流程呢?随着 Edsger Dijkstra 著名的《Goto
considered harmful》论文的出版,众人开始痛斥 goto 的不是,甚至建议从关键字集合中扫
地出门。


这种情况下,通常中庸之道是最好的。真正的问题并不在于使用 goto,而在于 goto 的滥
用。而且在一些少见的情况下,goto 是组织控制流程的最佳手段。
 
尽管 goto 仍是 Java 的一个保留字,但并未在语言中得到正式的使用;Java 没有 goto。
然而,在 break 和 continue 这两个关键字的身上,我们仍然能看出一些 goto 的影子。
它并不属于一次跳转,而是中断循环语句的一种方法。之所以把它们纳入 goto 问题中一起
讨论,是由于它们使用了相同的机制:标签(label)。


“标签”是后面跟一个冒号的标识符,就象下面这样:


label1:


在 Java 中,标签起作用的唯一的地方是在迭代语句之前。在标签和迭代之间置入任何语句
都是不明智的。而在迭代之前设置标签的唯一理由是:我们希望在其中嵌套另一个循环或者
一个开关。这是由于 break 和 continue 关键字通常只中断当前循环,但若随同标签使用,它
们就会中断所有进行中的循环,转到标签所在的地方:




label1:  
outer-iteration {
  inner-iteration {
//...
break; // 1
//...
continue;  // 2
//...
continue label1; // 3
//...
break label1;  // 4
  } 
}




在状况 1 处,break 中断内部循环,回到外部循环。在状况 2 处,continue 移回内部循
环的起始处。状况 3 处,continue label1 同时中断内部循环以及外部循环,直接转到
label1 处。随后,它实际是继续循环,但却从外部循环重新开始。在状况 4 处,break
label1 也会中断所有循环,并回到 label1 处,但并不重新进入循环。也就是说,它实际
是完全中止了两个循环。


下面是 Label 用于 for 循环的一个例子:




//: c03:LabeledFor.java
// Java's "labeled for" loop.
import com.bruceeckel.simpletest.*;




public class LabeledFor {
static Test monitor = new Test();
public static void main(String[] args) {
 
int i = 0;
    outer: // Can't have statements here
for(; true ;) { // infinite loop
      inner: // Can't have statements here
for(; i < 10; i++) {
        System.out.println("i = " + i);
if(i == 2) {
          System.out.println("continue");
continue;
        } 
if(i == 3) {
          System.out.println("break");
          i++; // Otherwise i never
               // gets incremented.
break;
        } 
if(i == 7) {
          System.out.println("continue outer");
          i++; // Otherwise i never
               // gets incremented.
continue outer;
        } 
if(i == 8) {
          System.out.println("break outer");
break outer;
        } 
for(int k = 0; k < 5; k++) {
if(k == 3) {
            System.out.println("continue inner");
continue inner;
          } 
        } 
      } 
    } 
    // Can't break or continue to labels here
    monitor.expect(new String[] {
"i = 0",
"continue inner",
"i = 1",
"continue inner",
"i = 2",
"continue",
"i = 3",
"break",
"i = 4",
"continue inner",
"i = 5",
"continue inner",
"i = 6",
"continue inner",
"i = 7",
"continue outer",
"i = 8",
"break outer"
    });
  } 
} ///:~

注意,break 会中断 for 循环,而且在抵达 for 循环的末尾之前,递增表达式不会执行。
由于 break 跳过了递增表达式,作为弥补,在 i==3 的情况下,我们直接对 i 执行递增运
算加 1。在 i==7 的情况下,continue outer 语句会跳到循环顶部,而且也会跳过递增,
所以这里也对 i 直接加 1 递增。


如果没有 break outer 语句,就没有办法从一个内部循环里找到跳出外部循环的路径。
这是由于 break 本身只能中断最内层的循环(对于 continue 同样如此)。


当然,如果想在中断循环的同时退出方法,简单地用一个 return 即可。


下面这个例子向大家展示了带标签的 break 以及 continue 语句在 while 循环中的用法:


//: c03:LabeledWhile.java
// Java's "labeled while" loop.
import com.bruceeckel.simpletest.*;
public class LabeledWhile {
static Test monitor = new Test();
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;
        } 
      } 
    } 
    monitor.expect(new String[] {
"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。


在 Dijkstra 的“Goto 有害”的论文中,他最反对的就是标签,而非 goto。他发现在一个程序里
 
随着标签的增多,产生的错误也越来越多。由于标签和 goto 在程序执行流程图中引入了循
环(cycles),使我们难以对程序作静态分析。但是,Java 的标签不会造成这种问题,因为它
们的应用场合已经受到限制了,不能用什么特别的方式改变程序的控制流程。由此也引出了

一个有趣的问题:通过限制语句的能力,反而能使一项语言特性更加有用。


你可能感兴趣的:(臭名昭著的“goto”)