函數式編程,同面向對象編程、指令式編程同樣,是一種軟件編程範式,在多種編程語言中都有應用。百科詞條中有很學術化的解釋,但理解起來並不容易。不過,咱們能夠藉助於數學中函數的概念,來理解函數式編程的要義所在。在數學中,咱們常見的函數表達式形如 y=f(x),表示的是一種輸入輸出的映射關係:x表示輸入,y表示輸出,f 是表示二者之間的映射運算邏輯。在求值的時候,你徹底不用考慮映射運算 f,只要給定輸入 x,獲得相應的輸出 y;輸入不變,輸出也不會改變,就這麼簡單。類比到程序語言中來,所謂函數式編程,就是讓咱們以數學中函數映射的思想來編寫出函數式的程序代碼,讓代碼着重於輸入和輸出,而底層的映射處理邏輯,你徹底能夠當黑盒看待,這樣,咱們的業務關注點會更加清晰;並且,同數學函數同樣,函數式編程的代碼具備狀態無關性——即相同的輸入永遠產生相同的輸出,這在解決併發編程中共享變量狀態一致性問題中有很大的應用場景。java
在Java中,提到函數式編程,最早想到的確定是Lambda表達式了(PS:切忌把Lambda表達式和函數式編程劃等號,Lambda表達式只是符合這種函數式編程風格的匿名函數而已)。Lambda表達式在Java8中終於被重磅引入了(隔壁Python,C#,C++早就引入了喲喂),這讓不少之前代碼中的匿名寫法得以經過函數式的代碼進行極致的簡化,有多簡化呢?好比使用IDEA開發的時候,若是你選擇的Java編譯版本達到Java8的話,在編寫匿名內部類的時候,編譯器會不厭其煩的提示你將匿名寫法替換成Lambda表達式——編程
你替換之後,原來幾行的代碼就簡化成了下面這個樣子——segmentfault
這就是讓你初見懵逼,再見着迷的函數式編程範例了。常有人說,相較於匿名內部類的寫法,Lambda 表達式使代碼更加簡潔、易讀。簡潔確實簡潔,畢竟減小了不少樣板代碼,非要說易讀,博主是有些遲疑的。從語法上來講,除非你對Java8的這種新特性有過至關的學習,不然剛開始接觸這種寫法,你反而會有些凌亂,不解其意。喜感的是,這種只強調輸入和輸出的函數式編程風格其顯著優勢恰是讓你一見代碼就知其業務內容;你之因此會凌亂,在於其語法相較於咱們面向對象編程中熟悉的類、接口、字段等概念太過新穎了,須要必定的學習成本。 併發
在上面的代碼示例中,咱們之因此寫匿名內部類,是由於在單一業務場景中,咱們不想額外的編寫接口的實現再去構造對象執行方法,而是直接建立匿名對象,執行完接口中的方法,對象的使命也就結束了。相較於先實現再建對象的方式,匿名內部類的寫法算是一種代碼的減省,雖然可讀性差了些,但咱不怕!難受的一點在於,即便匿名寫法,依然要遵循接口實現的規範,會多出不少樣板式的代碼;而實際上,我不過是想基於接口的方法定義去實現某種行爲而已。也就是說,個人關注點在於接口的行爲實現,而不是樣板的語法層面。這個時候,Lambda表達式就得以大顯身手了,它如你所願,讓你以函數式的編碼風格,只關注行爲自己的邏輯實現,其它的無關代碼統統捨棄。就像上面的示例中,將傳統的匿名寫法改爲 Lambda 表達式寫法後,樣板代碼沒了,簡潔的代碼讓你一眼就能看出,你的代碼要幹什麼。——這,就是Lambda!雖然初見時確實有些語法障礙,但在突破障礙以後,你會從心的喜歡這種編程方式——至少,在你的代碼走位中應該適時的加入些 Lambda 這種風騷的‘姿式’了。異步
有人說,不就是代碼簡化嘛,語法糖而已啦。關於 Lambda 表達式是否是語法糖的說法,又能夠開篇講義了。只能簡單的說,Lambda 確實也是語法糖,但毫不是簡單隻爲簡化匿名內部類的寫法的語法糖。固然,除了國內大神們的探究,你也能夠去 Stack Overflow上提問或搜尋答案,看看老外們都怎麼解釋的。編程語言
Lambda 表達式的我的理解,其實上文中已經給出了。如今,咱們從語法層面,來講說實際項目中該如何編寫基於 Lambda 的函數式風格代碼。博主以上面的代碼爲例,整了一副草圖,幫助你快速讀懂 Lambda 語法:函數式編程
這只是最簡單的形式。空括號 () 表示沒有輸入參數,若是匿名接口有參數,你按照正常方法的參數定義編寫便可,如 (Object o),(Object o1,Object o2)等。但因爲Java7 開始就有了類型推斷,一般咱們是能夠省略參數類型的,因此參數能夠簡化成 (o) ,(o1,o2)的形式,甚至在只有一個參數的時候,括號也能省略而只保留參數 o 。至於表達式的主體部分,也就是咱們的業務代碼,既能夠是是一個表達式,如上面的一條打印,也能夠是用花括號 { } 包圍的一段代碼塊,具體以實際業務爲準,固然,也要考慮代碼的可讀性。最後列舉一些常見 Lambda 代碼示例:函數
// 開啓普通的線程任務 new Thread(() -> System.out.println("函數式編程——陳本布衣")).start(); // 能夠是代碼塊的形式 new Thread(() ->{ System.out.println("函數式編程中的代碼塊"); System.out.println("函數式編程中的代碼塊"); System.out.println("函數式編程中的代碼塊"); System.out.println("函數式編程中的代碼塊"); }).start(); // 開啓異步單線程,可獲取線程任務返回值 Future<Integer> future = Executors.newSingleThreadExecutor().submit(() -> 10); // GUI圖形界面編程中的事件處理 new JButton().addActionListener((ActionEvent event) -> System.out.println("按鈕點擊事件")); // 參數根據上下文推斷,單參數可省略括號 () new JButton().addActionListener(event -> System.out.println("按鈕點擊事件"));
友情提示:因爲 Lambda對代碼的極致簡化和新語法,初學者很難一步到位的寫出正確的 Lambda 表達式代碼,對初學者,比較好的實踐建議先用匿名內部類的形式先實現,最後藉助於IDE的快捷功能自動生成,待熟練以後,再裝逼不遲!性能
只學會了 Lambda 表達式的語法還遠遠不夠,由於你不光要能手擼 Lambda 表達式代碼,更重要的是你要搞清楚,在哪一種場景下能夠擼,哪一種場景下沒法擼,這是有講究的。雖然上文中舉了幾個示例,但在實際應用中是遠遠不夠的。博主說過,Lambda 表達式本質上是一個匿名函數,這麼說,難道只要接口採用匿名類實現的地方,均可以使用Lambda 嗎?答案固然是否認的!你能夠親自試一下,本身編寫一個多方法的接口,也採用匿名實現,你看IDEA會不會那麼熱情的提醒你。學習
其實,在Java8 中伴隨 Lambda 一塊兒引入的,還有函數式接口這一律念。所謂函數式接口,是隻有一個抽象方法的接口,只有這種接口才能被用來做爲 Lambda 表達式的類型——也就是說,只有函數式接口的匿名實現,你才能夠用 Lambda 表達式去改寫代碼。感受這種限定縮窄了Lambda的應用範圍,我上哪兒給你找那麼多隻有一個抽象函數的接口啊?有的,有的,並且還很多。Java8 爲了支持函數式編程的應用場景,特地新增長了一個全是函數式接口的包 java.util.function,裏面包含了四十多個函數式接口,足夠你玩一陣子的了,並且這還不包括舊有的接口中符合函數接口定義的衆多接口,如 Runnable,Comparator<T>,以及Java8中爲了更便利的操做集合而新增的特性類庫等。從 Java8 開始,你在源碼中能夠發現,不管舊有的和新引入的函數式接口,其接口聲明上都會有 @FunctionalInterface 註解,該註解其實就是專門用來標註函數式接口的,算是一個標識註解。固然,也不是說函數接口就必定要用標示 @FunctionalInterface 註解來標註,只要符合只有一個抽象方法的接口定義,沒有 @FunctionalInterface 標註也能成爲 Lambda表達式的類型,只不過在接口上加上註解(尤爲本身在定義函數式接口的時候),可讓編譯器幫你檢查錯誤。
函數接口,說這麼多其實差很少就算完整了,可是且慢,博主仍是要糾結一下:只有一個抽象方法的接口,是爲函數式接口,那麼,是否是不止一個抽象方法的接口,就必定不是抽象接口呢?上一段的闡述中,布衣博主故意列了一個 Comparator<T>接口,其在Java8 中的源碼以下:
@FunctionalInterface public interface Comparator<T> { int compare(T o1, T o2); boolean equals(Object obj); // 省略非抽象的 靜態 和 默認方法 。。。 }
咦,這廝不對啊,有兩個抽象接口,怎麼也成了函數式接口?遇到這種狀況,切莫慌張,找尋答案最好的方式,仍是要從該接口聲明的註釋中去找,萬一寫源碼的人搞錯了呢?固然,錯確定是錯不了,不過 Comparator 接口聲明的註釋中也沒有給出合理的解釋,仍是隻能從源頭 @FunctionalInterface 註解的註釋中去看看有沒有答案。在 @FunctionalInterface 註解的註釋文檔中你會找到這樣的描述:
If an interface declares an abstract method overriding one of the public methods of {@code java.lang.Object}, that also does not count toward the interface's abstract method count since any implementation of the interface will have an implementation from {@code java.lang.Object} or elsewhere。
這算是很白話的英語了,簡單翻譯一下就是,由於全部接口都是默認繼承了Object類的,因此,接口中若是有自 Object 類中繼承而來的public方法,就不能算成抽象方法了。
好啦,對於函數式編程講解的的開篇,算是講完了。但這僅僅是開始,對於函數式編程這樣一種新的編程嘗試,還有不少值得學習和討論的地方。後續博主會繼續深刻探究 Java8 中針對函數式編程引入的一些方法類庫,以及這些新特性能給咱們的編碼帶來哪些便利。
限於法力有限,只能粗淺講解;歡迎挑刺,不勝感激。