Java程式設計邏輯-函數語言程式設計1
第一章 你好,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提供的這個新特性,改變了我們原有的程式設計方式,使得我們寫出的程式碼不僅簡潔優雅,不易出錯,而且效率更高,易於優化改進和並行化。