goto 關鍵字很早就在程式設計語言中出現。事實上,goto 是組合語言的程式控制結構的始祖:“若條件 A,
則跳到這裡;否則跳到那裡”。若閱讀由幾乎所有編譯器產生的彙編代碼,就會發現程式控制裡包含了許多
跳轉。然而,goto 是在源碼的層級跳轉的,所以招致了不好的聲譽。若程式總是從一個地方跳到另一個地
方,還有什麼辦法能識別代碼的流程呢。隨著 Edsger Dijkstra 著名的“Goto 有害”論的問世,goto 便從此
失寵。
事實上,真正的問題並不在於使用 goto ,而在於 goto 的濫用。而且在一些少見的情況下,goto 是組織控制
流程的最佳手段。
儘管 goto 仍是 Java 的一個保留字,但並未在語言中得到正式使用;Java 沒有 goto 。然而,在 break 和
continue 這兩個關鍵字的身上,我們仍然能看出一些 goto 的影子。它並不屬於一次跳轉,而是中斷迴圈語
句的一種方法。之所以把它們納入 goto 問題中一起討論,是由於它們使用了相同的機制:標籤。
“標籤”是後面跟一個冒號的標識符,就象下面這樣:
label1:
對 Java 來說,唯一用到標籤的地方是在迴圈語句之前。進一步說,它實際需要緊靠在迴圈語句的前方——在
標籤和迴圈之間置入任何語句都是不明智的。而在迴圈之前設定標籤的唯一理由是:我們希望在其中嵌套另
一個迴圈或者一個開關。這是由於 break 和 continue 關鍵字通常只中斷當前迴圈,但若隨同標籤使用,它們
就會中斷到存在標籤的地方。如下所示:
label1:
外部迴圈{
內部迴圈{
//...
break; //1
//...
continue; //2
//...
continue label1; //3
//...
break label1; //4
}
}
在條件 1 中,break 中斷內部迴圈,並在外部迴圈結束。在條件 2 中,continue 移回內部迴圈的起始處。但
在條件 3 中,continue label1 卻同時中斷內部迴圈以及外部迴圈,並移至 label1 處。隨後,它實際是繼續
迴圈,但卻從外部迴圈開始。在條件 4 中,break label1 也會中斷所有迴圈,並回到 label1 處,但並不重
新進入迴圈。也就是說,它實際是完全中止了兩個迴圈。
下面是 for 迴圈的一個例子:
// : LabeledFor.java // Java’s "labeled for loop" public class LabeledFor { 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++) { prt("i = " + i); if (i == 2) { prt("continue"); continue; } if (i == 3) { prt("break"); i++; // Otherwise i never // gets incremented. break; } if (i == 7) { prt("continue outer"); i++; // Otherwise i never // gets incremented. continue outer; } if (i == 8) { prt("break outer"); break outer; } for (int k = 0; k < 5; k++) { if (k == 3) { prt("continue inner"); continue inner; } } } } // Can't break or continue // to labels here } static void prt(String s) { System.out.println(s); } } // /:~
這裡用到了在其他例子中已經定義的 prt()方法。
注意 break 會中斷 for 迴圈,而且在抵達 for 迴圈的末尾之前,遞增運算式不會執行。由於 break 跳過了遞
增運算式,所以遞增會在 i==3 的情況下直接執行。在 i==7 的情況下,continue outer 語句也會到達迴圈頂
部,而且也會跳過遞增,所以它也是直接遞增的。
下面是輸出結果:
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 outer 語句,就沒有辦法在一個內部迴圈裡找到出外部迴圈的路徑。這是由於 break 本身只
能中斷最內層的迴圈(對於 continue 同樣如此)。
當然,若想在中斷迴圈的同時退出方法,簡單地用一個 return 即可。
下面這個例子向大家展示了帶標籤的 break 以及 continue 語句在 while 迴圈中的用法:
// : LabeledWhile.java // Java's "labeled while" loop public class LabeledWhile { public static void main(String[] args) { int i = 0; outer: while (true) { prt("Outer while loop"); while (true) { i++; prt("i = " + i); if (i == 1) { prt("continue"); continue; } if (i == 3) { prt("continue outer"); continue outer; } if (i == 5) { prt("break"); break; } if (i == 7) { prt("break outer"); break outer; } } } } static void prt(String s) { System.out.println(s); } } // /:~
同樣的規則亦適用於 while:
(1) 簡單的一個 continue 會退回最內層迴圈的開頭(頂部),並繼續執行。
(2) 帶有標籤的 continue 會到達標籤的位置,並重新進入緊接在那個標籤後面的迴圈。
(3) break 會中斷當前迴圈,並移離當前標籤的末尾。
(4) 帶標籤的 break 會中斷當前迴圈,並移離由那個標籤指示的迴圈的末尾。
這個方法的輸出結果是一目瞭然的:
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
大家要記住的重點是:在 Java 裡唯一需要用到標籤的地方就是擁有嵌套迴圈,而且想中斷或繼續多個嵌套級
別的時候。
在 Dijkstra 的“Goto 有害”論中,他最反對的就是標籤,而非 goto 。隨著標籤在一個程式裡數量的增多,
他發現產生錯誤的機會也越來越多。標籤和 goto 使我們難於對程式作靜態分析。這是由於它們在程式的執行
流程中引入了許多“怪圈”。但幸運的是,Java 標籤不會造成這方面的問題,因為它們的活動場所已被限
死,不可通過特別的方式到處傳遞程式的控制權。由此也引出了一個有趣的問題:通過限制語句的能力,反
而能使一項語言特性更加有用。
摘自《java編程思想》