本系列文章譯自Venkat Subramaniam的Functional Programming in Java
未完待續,后續文章請繼續關注deepinmind。
第一章 你好,lambda表達式!
第一節
Java的編碼風格正面臨着翻天覆地的變化。我們每天的工作將會變成更簡單方便,更富表現力。Java這種新的編程方式早在數十年前就已經出現在別的編程語言里面了。這些新特性引入Java后,我們可以寫出更簡潔,優雅,表達性更強,錯誤更少的代碼。我們可以用更少的代碼來實現各種策略和設計模式。在本書中我們將通過日常編程中的一些例子來探索函數式風格的編程。在使用這種全新的優雅的方式進行設計編碼之前,我們先來看下它到底好在哪里。
改變了你的思考方式
命令式風格——Java語言從誕生之初就一直提供的是這種方式。使用這種風格的話,我們得告訴Java每一步要做什么,然后看着它切實的一步步執行下去。這樣做當然很好,就是顯得有點初級。代碼看起來有點啰嗦,我們希望這個語言能變得稍微智能一點;我們應該直接告訴它我們想要什么,而不是告訴它如何去做。好在現在Java終於可以幫我們實現這個願望了。我們先來看幾個例子,了解下這種風格的優點和不同之處。
正常的方式
我們先從兩個熟悉的例子來開始。這是用命令的方式來查看芝加哥是不是指定的城市集合里——記住,本書中列出的代碼只是部分片段而已。
boolean found = false; for(String city : cities) { if(city.equals("Chicago")) { found = true; break; } } System.out.println("Found chicago?:" + found);
這個命令式的版本看起來有點啰嗦而且初級;它分成好幾個執行部分。先是初始化一個叫found的布爾標記,然后遍歷集合里的每一個元素;如果發現我們要找的城市了,設置下這個標記,然后跳出循環體;最后打印出查找的結果。
一種更好的方式
細心的Java程序員看完這段代碼后,很快會想到一種更簡潔明了的方式,就像這樣:
System.out.println("Found chicago?:" + cities.contains("Chicago"));
這也是一種命令式風格的寫法——contains方法直接就幫我們搞定了。
實際改進的地方
- 代碼這么寫有這幾個好處:
- 不用再搗鼓那個可變的變量了
- 將迭代封裝到了底層
- 代碼更簡潔代碼
- 更清晰,更聚焦
- 少走彎路,代碼和業務需求結合更密切
- 不易出錯
- 易於理解和維護
來個復雜點的例子
這個例子太簡單了,命令式查詢一個元素是否存在於某個集合在Java里隨處可見。現在假設我們要用命令式編程來進行些更高級的操作,比如解析文件 ,和數據庫交互,調用WEB服務,並發編程等等。現在我們用Java可以寫出更簡潔優雅同時出錯更少的代碼,更不只是這種簡單的場景。
老的方式
我們來看下另一個例子。我們定義了一系列價格,並通過不同的方式來計算打折后的總價。
final List prices = Arrays.asList( new BigDecimal("10"), new BigDecimal("30"), new BigDecimal("17"), new BigDecimal("20"), new BigDecimal("15"), new BigDecimal("18"), new BigDecimal("45"), new BigDecimal("12"));
假設超過20塊的話要打九折,我們先用普通的方式實現一遍。
BigDecimal totalOfDiscountedPrices = BigDecimal.ZERO; for(BigDecimal price : prices) { if(price.compareTo(BigDecimal.valueOf(20)) > 0) totalOfDiscountedPrices = totalOfDiscountedPrices.add(price.multiply(BigDecimal.valueOf(0.9))); } System.out.println("Total of discounted prices: " + totalOfDiscountedPrices);
這個代碼很熟悉吧;先用一個變量來存儲總價;然后遍歷所有的價格,找出大於20塊的,算出它們的折扣價,並加到總價里面;最后打印出折扣后的總價。下面是程序的輸出:
Total of discounted prices: 67.5
結果完全正確,不過這樣的代碼有點亂。這並不是我們的錯,我們只能用已有的方式來寫。不過這樣的代碼實在有點初級,它不僅存在基本類型偏執,而且還違反了單一職責原則。如果你是在家工作並且家里還有想當碼農的小孩的話,你可得把你的代碼藏好了,萬一他們看見了會很失望地嘆氣道,“你是靠這些玩意兒糊口的?”
還有更好的方式
我們還能做的更好——並且要好很多。我們的代碼有點像需求規范。這樣能縮小業務需求和實現的代碼之間的差距,減少了需求被誤讀的可能性。我們不再讓Java去創建一個變量然后沒完沒了的給它賦值了,我們要從一個更高層次的抽象去與它溝通,就像下面的這段代碼。
final BigDecimal totalOfDiscountedPrices = prices.stream() .filter(price -> price.compareTo(BigDecimal.valueOf(20)) > 0) .map(price -> price.multiply(BigDecimal.valueOf(0.9))) .reduce(BigDecimal.ZERO, BigDecimal::add); System.out.println("Total of discounted prices: " + totalOfDiscountedPrices);
大聲的讀出來吧——過濾出大於20塊的價格,把它們轉化成折扣價,然后加起來。這段代碼和我們描述需求的流程簡直一模一樣。Java里還可以很方便的把一行長的代碼折疊起來,根據方法名前面的點號進行按行對齊,就像上面那樣。代碼非常簡潔,不過我們用到了Java8里面的很多新東西。首先,我們調用 了價格列表的一個stream方法。這打開了一扇大門,門后邊有數不盡的便捷的迭代器,這個我們在后面會繼續討論。我們用了一些特殊的方法,比如filter和map,而不是直接的遍歷整個列表。這些方法不像我們以前用的JDK里面的那些,它們接受一個匿名的函數——lambda表達式——作為參數。(后面我們會深入的展開討論)。我們調用reduce()方法來計算map()方法返回的價格的總和。就像contains方法那樣,循環體被隱藏起來了。不過map方法(以及filter方法)則更復雜得多 。它對價格列表中的每一個價格,調用了傳進來的lambda表達式進行計算,把結果放到一個新的集合里面。最后我們在這個新的集合上調用 reduce方法得出最終的結果。這是以上代碼的輸出結果:
Total of discounted prices: 67.5
改進的地方
- 這和前面的實現相比改進明顯:
- 結構良好而不混亂
- 沒有低級操作
- 易於增強或者修改邏輯
- 由方法庫來進行迭代
- 高效;循環體惰性求值
- 易於並行化
下面我們會說到Java是如何實現這些的。
lambda表達式來拯救世界了
lambda表達式是讓我們遠離命令式編程煩惱的快捷鍵。Java提供的這個新特性,改變了我們原有的編程方式,使得我們寫出的代碼不僅簡潔優雅,不易出錯,而且效率更高,易於優化改進和並行化。
未完待續,后續文章請繼續關注deepinmind。
原創文章轉載請注明出處:Java譯站