1. 程式人生 > >Java 8 習慣用語,第 3 部分 傳統 for 迴圈的函式式替代方案

Java 8 習慣用語,第 3 部分 傳統 for 迴圈的函式式替代方案

原文地址:https://www.ibm.com/developerworks/cn/java/j-java8idioms3/index.html

Java 8 習慣用語,第 3 部分

傳統 for 迴圈的函式式替代方案

3 個消除複雜迭代中的麻煩的新方法

儘管 for 迴圈包含許多可變部分,但許多開發人員仍非常熟悉它,並會不假思索地使用它。從 Java™ 8 開始,我們有多個強大的新方法可幫助簡化複雜迭代。在本文中,您將瞭解如何使用 IntStream 方法 rangeiterate 和 limit 來迭代範圍和跳過範圍中的值。您還將瞭解新的 takeWhile 和 dropWhile

 方法(即將在 Java 9 中引入)。

關於本系列

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

,將它限制到小於 4 的值。請注意,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 中,我們看到並沒有顯著減少程式碼量,但降低了它的複雜性。這樣做有兩個重要原因:

  1. 不同於 forrange 不會強迫我們初始化某個可變變數。
  2. 迭代會自動執行,所以我們不需要像迴圈索引一樣定義增量。

在語義上,最初的 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 是一個函式介面,所以我們可以輕鬆地將匿名的內部類替換為拉姆達表示式,比如:

清單 5. 將內部類替換為拉姆達表示式