深入理解函數式編程



函數式編程是對行為進行抽象。

編程一生,公眾號:編程一生架構之思-分析那些深入骨髓的設計原則

這句話比較難理解,換句話來說:函數式編程是給自己的對象整容,有可能整的和原來差不多,也有可能整的看起來判若兩人,但是只能處理這個對象,不會對函數外的其他數據產生影響。

函數式編程又結合了lambda表達式和stream API。有些朋友反饋說:函數式編程可讀性不好;還有些朋友反饋說:函數式編程比較難debug。你們說的都對,但是有解決的辦法,看完這篇文章就明白了。

文章整體大綱如下:

lambda表達式

lambda表達式的本質是匿名內部類

先來看一個例子:杜甫的《登高》寫的好,被稱為千古律詩之首。

我從十幾歲的時候開始就一直在想這首詩怎么不押韻:渚清沙白鳥飛回,高中老師講過古語里“回”念huai,這就壓上韻了。但是潦倒新停濁酒杯的杯在古語或者古語方言里念bai嗎?直到如今我還是沒有考證到是否是這樣。我就當它是念bai吧。

這首詩我最喜歡的四句,渲染磅礴的氣勢都含了數字:萬里、百年、無邊、不盡。我突發奇想:讓電腦來給這四句排排序吧。於是我寫了下面的程序:

public void sortDengGao() {
List<String> list = Lists.newArrayList("無邊落木蕭蕭下","不盡長江滾滾來","萬里悲秋常作客","百年多病獨登台");
list.sort(new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o1.substring(0, 2).compareTo(o2.substring(0, 2));
}
});
System.out.println(list);
}

我鼠標點在new Comparator上,提示我匿名內部類可以用lambda代替:

使用Alt+Enter快捷鍵,我回車一下,結果變成了這樣:

public void sortDengGao() {
List<String> list = Lists.newArrayList("無邊落木蕭蕭下","不盡長江滾滾來","萬里悲秋常作客","百年多病獨登台");
list.sort((o1, o2) -> o1.substring(0, 2).compareTo(o2.substring(0, 2)));
System.out.println(list);
}

當然還可以再執行一次Alt+Enter還原回來。

匿名內部類和lambda表達式既然可以使用快捷鍵相互轉換,那就說明他們本質上是一個東西。這樣就好理解了:在jdk1.8之前,通過匿名內部類訪問局部變量必須要加final關鍵字,jdk1.8之后不需要顯示的加final關鍵字但實際上還是需要被訪問的變量不可變。這就對應了函數式編程不會對函數外的其他數據產生影響。

lambda表達式的省略規則

lambda表達式核心是(對於匿名內部類)采用可推導可省略的原則。所以有些朋友反饋說:函數式編程可讀性不好。因為它做了省略,如果對原本被省略的匿名內部類不熟悉,閱讀就會麻煩些。還有一點哈,編寫代碼注意換行哈:

list.stream().sorted((o1, o2) -> o1.substring(0, 2).compareTo(o2.substring(0, 2))).close();

這段代碼一個方法用一行是不是好看一些:

list.stream()
.sorted((o1, o2) -> o1.substring(0, 2).compareTo(o2.substring(0, 2)))
.close();

lambda表達式是對匿名內部類的下面三點做了省略:

  • 參數類型可省略

  • 方法只有一句代碼時:大括號、return和分號可省略

  • 方法只有一個參數時小括號可以省略

 

stream API

stream API就是運用fluent風格的一個特例

對fluent風格不熟悉的強烈建議看看我之前的這篇《代碼榮辱觀-以運用風格為榮,以隨意編碼為恥》。這篇文章邏輯清晰,語言詼諧,比喻恰當,專治不明白。

這里只舉個簡單的例子:StringBuilder一般是這樣使用的:

new StringBuilder().append(1).append(2).toString();

這是典型的fluent風格。咱們來看它包含幾部分:

第一部分:new StringBuffer()構造一個特定對象

第二部分:append()對這個對象本身做處理,可以多次調用,每次都返回它本身

第三部分:toString()結束處理

再來看這個stream API的例子:

list.stream()
.sorted((o1, o2) -> o1.substring(0, 2).compareTo(o2.substring(0, 2)))
.close();

第一部分:.stream()構造一個特定對象

第二部分:sorted()對這個對象本身做處理,可以多次調用,每次都返回它本身

第三部分:close()結束處理

是不是一毛一樣!stream API就是運用fluent風格的一個特例,如此而已。所以我們要關注的點只是.stream()構造的特定對象Stream給我提供了怎么的功能,達到了號稱比sql還簡單、還強大的功能。

一分鍾理解MapReduce

MapReduce現在流行於大數據中的概念,本質上是為了解決對於數據的並行計算方法明明本質上都是采用分治法,但是缺少高層並行編程模型,程序員需要自行指定存儲、計算、分發等任務的問題。MapReduce借鑒了Lisp函數式語言中的思想,用map和reduce兩個函數提供了高層的並發編程模型的抽象。

直白點說就是提供了一個計算手腳架,照着這個架子做開發就可以了。

上面圖中可以看到map的主要功能是把數據分成小塊進行計算,reduce是將小塊計算結果進行合並。在stream API中map和reduce功能也是一樣的。舉個例子:

public void mapReduceDengGao() {
List<String> list = Lists.newArrayList("無邊落木蕭蕭下","不盡長江滾滾來","萬里悲秋常作客","百年多病獨登台");
String result = list.stream()
.map(word->word+"\n")
.reduce((a,b)->a+""+b)
.get();
System.out.println(result);
}

上面函數先用map方法把list每個元素都進行了處理:后面加換行符。然后用reduce方法對數據合並計算:合並為一個字符串。大數據的MapReduce也就是干了這!

用Intellij對stream API做debug

有些朋友反饋說:函數式編程比較難debug。stream trace了解一下。

首先在steam API的地方打上斷點。當運行到斷點處,點擊上面紅色框框里那個圖標,之后會彈出一個框,但是可能一開始沒有數據,提示正在計算,稍等一會之后stream的每一步調用結果都可以看到啦:

 

總結

函數式編程的優勢:

  • 代碼可讀性高

  • 大數據量下處理集合效率高

  • 消滅嵌套地獄

代碼可讀性高,上面已經講過了,因為簡潔明了。

大數據量下處理集合效率高,這個主要是指因為函數式編程功能內聚,JVM優化時去掉了多余的鎖。像上面MapReduce那段講的,采用分治法,使用並行流的話內部做了很好的多線程處理。

消滅嵌套地獄嘛,看看下面箭頭形代碼:

用函數式編程效果是這樣的,好看不好看不好說,起碼sonar靜態檢查能過:

 

推薦閱讀

年紀大了,是否該往管理方向轉型?

演講稿:新人培養之道

最近做code review的5點經驗分享

《躍遷-成為高手的技術》感悟


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM