臭名昭著的goto
程式設計語言中一開始就有 goto關鍵詞了。事實上, goto起源于彙編語言的程式控制:“若條件 A成立,則跳到這裡;否則跳到那裡”。如果閱讀由編譯器最終生成的彙編代碼,就會發現程式控制裡包含了許多跳轉。( Java編譯器生成它自己的“彙編代碼”,但是這個代碼是運作在 Java虛拟機上的,而不是直接運作在 CPU硬體上。) goto語句是在源碼級上的跳轉,這使其招緻了不好的聲譽。若一個程式總是從一個地方跳到另一個地方,還有什麼辦法能識别程式的控制流程呢?自從Edsger Dijkstra發表了著名論文 《Goto considered harmful》( Goto有害),衆人開始痛斥 goto的不是,甚至建議将它從關鍵字集合中掃地出門。 對于這個問題,中庸之道是最好解決方法。真正的問題并不在于使用 goto,而在于 goto的濫用;而且少數情況下, goto還是組織控制流程的最佳手段。
标簽是什麼
盡管 goto仍是 Java中的一個保留字,但在語言中并未使用它; Java沒有 goto。然而, Java也能完成一些類似于跳轉的操作,這與 break和 continue這兩個關鍵詞有關。它們其實不是一個跳轉,而是中斷疊代語句的一種方法。之是以把它們納入 goto問題中一起讨論,是由于它們使用了相同的機制:标簽。 标簽是後面跟有冒号的标示符,就像下面這樣:
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處,但并不重新進入疊代。也就是說,它實際是完全中止了兩個疊代。
示例源碼
下面是标簽用于 for循環和 while循環的例子:
package com.mufeng.thefourthchapter;
public class Labeled {
public static void main(String[] args) {
System.out.println("标簽用于for循環的例子");
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 否則i不會得到增量
break;
}
if (i == 7) {
System.out.println("continue outer");
i++;// Otherwise i never gets incremented 否則i不會得到增量
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;
}
}
}
}
System.out.println("标簽用于while循環的例子");
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;
}
}
}
}
}
輸出結果
标簽用于for循環的例子
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
标簽用于while循環的例子
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
源碼解析
注意, break會中斷 for循環,而且在抵達 for循環的末尾之前,遞增表達式不會執行。由于 break跳過了遞增表達式,是以在 i==3的情況下直接對 i執行遞增運算。在 i==7的情況下, continue outer語句會跳到循環頂部,而且也會跳過遞增,是以這裡也對 i直接遞增。 如果沒有 break outer語句,就沒辦法從内部循環裡跳出外部循環。這是由于 break本身隻能中斷最内層的循環( continue同樣也是如此)。 當然,如果想在中斷循環的同時退出,簡單地用一個 return即可。 同樣的規則亦适用于 while:
- 一般的continue會退回最内層循環的開頭(頂部),并繼續執行。
- 帶有标簽的continue會到達标簽的位置,并重新進入緊接在那個标簽後面的循環。
- 一般的break會中斷并跳出目前循環。
- 帶标簽的break會中斷并跳出标簽所指的循環。
要記住的重點是:在 Java裡需要使用标簽的唯一理由就是因為有循環嵌套存在,而且想從多層嵌套中 break或 continue。