在任何循環語句的主體部分,亦可用break和continue控制循環的流程。其中,break用於強行退出循環,不執行循環中剩余的語句。而continue則停止執行當前的反復,然後退回循環起始和,開始新的反復。
下面這個程序向大家展示了break和continue在for和while循環中的例子:
//: BreakAndContinue.java
// Demonstrates break and continue keywords
public class BreakAndContinue {
public static void main(String[] args) {
for(int i = 0; i < 100; i++) {
if(i == 74) break; // Out of for loop
if(i % 9 != 0) continue; // Next iteration
System.out.println(i);
}
int i = 0;
// An "infinite loop":
while(true) {
i++;
int j = i * 27;
if(j == 1269) break; // Out of loop
if(i % 10 != 0) continue; // Top of loop
System.out.println(i);
}
}
} ///:~
在這個for循環中,i的值永遠不會到達100。因為一旦i到達74,break語句就會中斷循環。通常,只有在不知道中斷條件何時滿足時,才需象這樣使用break。只要i不能被9整除,continue語句會使程序流程返回循環的最開頭執行(所以使i值遞增)。如果能夠整除,則將值顯示出來。
第二部分向大家揭示了一個“無限循環”的情況。然而,循環內部有一個break語句,可中止循環。除此以外,大家還會看到continue移回循環頂部,同時不完成剩余的內容(所以只有在i值能被9整除時才打印出值)。輸出結果如下:
0
9
18
27
36
45
54
63
72
10
20
30
40
之所以顯示0,是由於0%9等於0。
無限循環的第二種形式是for(;;)。編譯器將while(true)與for(;;)看作同一回事。所以具體選用哪個取決於自己的編程習慣。
1. 臭名昭著的“goto”
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標簽不會造成這方面的問題,因為它們的活動場所已被限死,不可通過特別的方式到處傳遞程序的控制權。由此也引出了一個有趣的問題:通過限制語句的能力,反而能使一項語言特性更加有用。