Java 8 習慣用語,第 3 部分 傳統 for 迴圈的函式式替代方案
原文地址:https://www.ibm.com/developerworks/cn/java/j-java8idioms3/index.html
Java 8 習慣用語,第 3 部分
傳統 for 迴圈的函式式替代方案
3 個消除複雜迭代中的麻煩的新方法
儘管 for
迴圈包含許多可變部分,但許多開發人員仍非常熟悉它,並會不假思索地使用它。從 Java™ 8 開始,我們有多個強大的新方法可幫助簡化複雜迭代。在本文中,您將瞭解如何使用 IntStream
方法 range
、iterate
和 limit
來迭代範圍和跳過範圍中的值。您還將瞭解新的 takeWhile
和 dropWhile
Java 8 是自 Java 語言誕生以來進行的一次最重大更新 — 包含了非常豐富的新功能,您可能想知道從何處開始著手瞭解它。在本系列中,作家兼教師 Venkat Subramaniam 提供了一種慣用的 Java 8 程式設計方法:這些簡短的探索會激發您反思您認為理所當然的 Java 約定,同時逐步將新技術和語法整合到您的程式中。
for 迴圈的麻煩
在 Java 語言的第 1 個版本中就開始引入了傳統的 for
迴圈,它的更簡單的變體 for-each
是在
Java 5 中引入的。大部分開發人員更喜歡使用 for-each
for
。
for
迴圈非常強大,但它包含太多可變部分。甚至在列印 get
set
提示的最簡單任務中,也可以看出這一點:
清單 1. 完成一個簡單任務的複雜程式碼
System.out.print("Get
set...");
for(int
i = 1; i < 4; i++) {
System.out.print(i
+ "...");
}
|
在清單 1 中,我們從 1 開始迴圈處理索引變數 i
for
迴圈需要我們告訴迴圈是遞增的。在本例中,我們還選擇了前遞增而不是後遞增。
清單 1 中沒有太多程式碼,但比較繁瑣。Java 8 提供了一種更簡單、更優雅的替代方法:IntStream
的 range
方法。以下是列印清單
1 中的相同 get set
提示的 range
方法:
清單 2. 完成一個簡單任務的簡單程式碼
System.out.print("Get
set...");
IntStream.range(1,
4)
.forEach(i
-> System.out.print(i + "..."));
|
在清單 2 中,我們看到並沒有顯著減少程式碼量,但降低了它的複雜性。這樣做有兩個重要原因:
-
不同於
for
,range
不會強迫我們初始化某個可變變數。 - 迭代會自動執行,所以我們不需要像迴圈索引一樣定義增量。
在語義上,最初的 for
迴圈中的變數 i
是一個可變變數。理解 range
和類似方法的價值對理解該設計的結果很有幫助。
可變變數與引數
for
迴圈中定義的變數 i
是單個變數,它會在每次對迴圈執行迭代時發生改變。range
示例中的變數 i
是拉姆達表示式的引數,所以它在每次迭代中都是一個全新的變數。這是一個細微區別,但決定了兩種方法的不同。以下示例有助於闡明這一點。
清單 3 中的 for
迴圈想在一個內部類中使用索引變數:
清單 3. 在內部類中使用索引變數
ExecutorService
executorService = Executors.newFixedThreadPool(10);
for(int
i = 0; i < 5; i++) {
int
temp = i;
executorService.submit(new
Runnable() {
public
void run() {
//If
uncommented the next line will result in an error
//System.out.println("Running
task " + i);
//local
variables referenced from an inner class must be final or effectively final
System.out.println("Running
task " + temp);
}
});
}
executorService.shutdown();
|
我們有一個匿名的內部類實現了 Runnable
介面。我們想在 run
方法中訪問索引變數 i
,但編譯器不允許這麼做。
作為此限制的解決辦法,我們可以建立一個區域性臨時變數,比如 temp
,它是索引變數的一個副本。每次新的迭代都會建立變數 temp
。在
Java 8 以前,我們需要將該變數標記為 final
。從 Java 8 開始,可以將它視為實際的最終結果,因為我們不會再更改它。無論如何,由於事實上索引變數是一個在迭代中改變的變數,for
迴圈中就會出現這個額外變數。
現在嘗試使用 range
函式解決同一個問題。
清單 4. 在內部類中使用拉姆達引數
ExecutorService
executorService = Executors.newFixedThreadPool(10);
IntStream.range(0,
5)
.forEach(i
->
executorService.submit(new
Runnable() {
public
void run() {
System.out.println("Running
task " + i);
}
}));
executorService.shutdown();
|
在作為一個引數被拉姆達表示式接受後,索引變數 i
的語義與迴圈索引變數有所不同。與清單 3 中手動建立的 temp
非常相似,這個 i
引數在每次迭代中都表現為一個全新的變數。它是實際最終變數,因為我們不會在任何地方更改它的值。因此,我們可以直接在內部類的上下文中使用它
— 且不會有任何麻煩。
因為 Runnable
是一個函式介面,所以我們可以輕鬆地將匿名的內部類替換為拉姆達表示式,比如: