1. 程式人生 > >Effective Java Third Edition學習 - Lambdas and Streams

Effective Java Third Edition學習 - Lambdas and Streams

Effective Java Third Edition學習 - Lambdas and Streams

Prefer lambdas to anonymous classes

匿名類太麻煩,讓Java不適合做函數語言程式設計。
Java8裡,只有一個方法的介面,被特殊對待。這些介面現在叫函式式介面,允許你使用 lambda表示式生成這些類的例項。Lambdas類似於匿名類,只是更簡潔。
忽略所有 lambda引數的型別吧,除非他們的存在使你的程式更清晰。如果編譯器生成一個錯誤,告訴你不能推斷lambda引數的型別,只能指定它。有時候,你不得不拋棄返回值或者整個 lambda表示式,但是,這太罕見了。
增加了 lambdas,可以無感知地使用函式物件。
不像方法和類,lambda缺乏名字和文件;如果計算不是不言自明的,或者超過了幾行,不要使用lambda。最理想的是隻有一行,最多三行。如果違反該規則,程式碼的可讀性就會變差。如果lambda太長,或者不好讀,要麼想辦法簡化,要麼別用。
同樣,你可能覺得匿名類過時了。這接近真相,但是,有時候你能用匿名類而不能用lambda。lambda被限制成函式式介面。如果你想生成一個抽象類的例項,你可以用匿名類,不能用lambda。同樣,你能用匿名類生成有多個抽象方法的介面的例項。最後,lambda不能獲取它自己的引用。在lambda裡,關鍵字引用的是封閉的例項,它是你想要的那個型別。在匿名類裡,關鍵字引用的是該匿名類的例項。如果你需要訪問內部的函式物件,你必須用匿名類。
你不要序列化一個lambda(或者匿名類例項)。

Prefer method references to lambdas

lambdas和匿名類相比,最主要的優點是更簡潔。Java提供了一個比lambda還要簡潔的生成函式物件的辦法:方法引用。
你能用方法引用,就一定能用lambda(有一個晦澀的異常,見JLS, 9.9-2)。方法引用一般更短更清晰。如果一個lambda太長或者複雜,你可以從lambda裡抽取程式碼,放進一個新方法,然後,把lambda替換成新寫的方法。你能給方法起個好名字,和好文件。

Favor the use of standard functional interfaces

Java現在有了lambda,寫API的最佳做法有了比較大的變化。例如, Template Method模式[Gamma95],在子類裡覆蓋一個原始方法,沒什麼吸引力了。更好的辦法是提供一個靜態工廠或者構造器,接收一個函式物件。更一般地,你將寫更多的把函式物件作為引數的構造器和方法。要關心選擇正確的函式式引數型別。
java.util.function提供了很多標準函式式介面給你使用。如果一個標準函式式介面能做這工作,你應該使用它。這樣你的API容易學習。
如果標準的不能滿足你的需求,只好寫自己的函式式介面。例如,你需要使用三個引數做斷言,或者拋檢查式異常的介面。甚至跟標準的結構一致時,也應該寫自己的。
最後再注意一點,不要提供有多個過載的方法,不同的介面有相同的引數會增加歧義。這是個現實的問題。

Use streams judiciously

你應該不要用流處理char值。
當你開始使用流,你可能想用流替換所有的迴圈,但是,不要這樣。這樣將不好讀和維護。混合使用流和迭代,更適合完成複雜任務。
用程式碼塊,你能讀或者修改適用範圍內的區域性變數;用lambda,你只能讀final的或者有效的final變數[JLS 4.12.4],你不能修改任何區域性變數。
使用程式碼塊,你能從封閉的方法返回,break或者continue一個封閉的迴圈,能拋方法宣告的受檢異常;使用lambda,你做不到這些。
使用流,想在一個管道的多個步驟都訪問相應的元素,是很困難的:一旦做了一個值的對映,原始值就丟了。一個解決辦法是把原始值和新值對映成一對物件,這個 解決辦法不讓人滿意,特別是管道的多個步驟都需要物件對的時候。

Prefer side-effect-free functions in streams

流正規化的最重要部分是把你的計算構造成一系列變換,每一步的結果都儘可能成為前一步結果的純函式。純函式只依賴輸入就可以返回,跟狀態無關或者不更新任何狀態。為了實現它,你傳進流運算的任何函式物件,中間的和結束的,都應該沒有副作用。

Prefer Collection to Stream as a return type

使用介面卡,你能在任何流上,使用 for-each語句做迭代。
如果你寫的一個公共API,返回一個序列,你就同時應該提供給寫流管道和 for-each語句的使用者,除非你有好的理由相信大多數使用者都使用相同的機制。

Use caution when making streams parallel

不要不分青紅皁白地使用並行流管道。效能可能很差。
另一個重要的事實是,所有的這些資料結構的共同之處是,當順序處理時,他們提供了良好的區域性引用:在記憶體中,順序的元素引用儲存在一起。記憶體中,這些引用指向的物件可能不靠在一起,它減少了區域性引用。對於並行塊運算來說,區域性引用非常重要:沒有它,執行緒很多時候處於空閒狀態,等待資料從記憶體傳輸到處理器快取。擁有最好的區域性引用的資料結構是原始陣列,因為在記憶體裡,它的資料是連續儲存的。
如果你寫自己的 Stream、 Iterable或者Collection實現,想並行執行,你必須覆蓋 spliterator方法,廣泛測試效能。寫高質量的 spliterators是很困難的,超出了本書的範圍。
不只是並行的流能導致效能惡化,或者活性故障;它還能導致不正確的結果和不可預測的行為(安全故障)。使用並行流的mappers、filters 和程式提供的其他函式物件,沒有遵守他們的規則,可能導致安全故障。
通常,程式裡的所有並行流管道執行在 fork-join池內。一個行為不端的管道會危害系統內不相干部分的效能。
如果你用並行的流計算隨機數,SplittableRandom例項比 ThreadLocalRandom(或者過時的Random)好。 SplittableRandom就是為此設計的,能獲得線性提升。 ThreadLocalRandom是給單執行緒使用的,能適應並行流資料來源的功能,但是沒 SplittableRandom快。Random的每個運算都是同步的,導致過度的並行爭用。