小猿圈介紹java函數式編碼結構及優點

對於java你們都已經不陌生了吧,今天小猿圈Java講師就分享一篇關於java函數式編碼結構及優點的知識點,但願對於學習java的你有必定的幫助,想學習就須要積累。java


探討三種下一代JVM語言:Groovy、Scala和Clojure,比較並對比新的功能和範例,讓Java開發人員對本身近期的將來發展有大致的認識。程序員

當垃圾回收成爲主流時,它消除了全部類別的難以調試的問題,使運行時可以爲開發人員管理複雜的、容易出錯的進程。函數式編程旨在爲您編寫的算法實現一樣的優化,這樣您就能夠從一個更高的抽象層面開展工做,同時運行時執行復雜的優化。算法

Java下一代語言並不都佔用從命令式到函數式的語言頻譜的同一位置,但都展示出函數功能和習語。函數式編程技術有明肯定義,但語言有時爲相同的函數式概念使用不一樣的術語,使得咱們很難看到類似之處。在本期文章中,我比較了Scala、Groovy和Clojure的函數式編碼風格並討論了它們的優點。編程

命令式處理api

我要首先探討一個常見問題及其命令式解決方案。假如給定一個名稱列表,其中一些名稱包含一個字符。系統會要求您在一個逗號分隔的字符串中返回名稱,該字符串中不包含單字母的名稱,每一個名稱的首字母都大寫。實現該算法的Java代碼如清單1所示。數據結構

清單1.命令式處理多線程

public class TheCompanyProcess {併發

public String cleanNames(List<String> listOfNames) {app

StringBuilder result = new StringBuilder();框架

for(int i = 0; i < listOfNames.size(); i++) {

if (listOfNames.get(i).length() > 1) {

result.append(capitalizeString(listOfNames.get(i))).append(",");

}

}

return result.substring(0, result.length() - 1).toString();

}

public String capitalizeString(String s) {

return s.substring(0, 1).toUpperCase() + s.substring(1, s.length());

}

}

因爲您必須處理整個列表,解決清單1中問題最簡單的方式是使用一個命令式循環。對於每一個名稱,都須要進行檢查,確認其長度是否大於1,而後(若是長度大於1)將首字母大寫的名稱附加到result字符串,並在後面加逗號。最終字符串中的最後一個名稱不該包含逗號,因此我將它從最後返回值中移走。

在命令式編程中,建議您在較低級上別執行操做。在清單1中的cleanNames()方法中,我執行了三個任務:我篩選列表以消除單字符,將列表中每一個名稱的首字母變換爲大寫,而後將列表轉化爲一個字符串。在命令式語言中,我不得不爲三個任務都使用同一低級機制(對列表進行迭代)。函數式語言將篩選、變換和轉化視爲常見操做,所以它們提供給您從不一樣視角解決問題的方式。

函數式處理

函數編程語言與命令式語言的問題分類方式不一樣。篩選、變換和轉化邏輯類別表現爲函數。那些函數實現低級變換並依賴於開發人員來編寫做爲參數傳遞的函數,進而定製函數的行爲。我能夠用僞代碼將清單1中的問題概念化爲:

listOfEmps -> filter(x.length > 1) -> transform(x.capitalize) ->

convert(x, y -> x + "," + y)

利用函數式語言,您能夠建模這一律念性解決方案,無需擔憂實現細節。

Scala實現

清單2使用Scala實現清單1中的處理示例。它看起來就像是前面的僞代碼,包含必要的實現細節。

清單2.Scala處理

val employees = List("neal", "s", "stu", "j", "rich", "bob")

val result = employees

.filter(_.length() > 1)

.map(_.capitalize)

.reduce(_ + "," + _)

對於給定的名稱列表,我首先篩選它,剔除長度不大於1的全部名稱。而後將該操做的輸出提供給map()函數,該函數對集合的每一個元素執行所提供的代碼塊,返回變換後的集合。最後,來自map()的輸出集合流向reduce()函數,該函數基於代碼塊中提供的規則將每一個元素結合起來。

在本例中,我將每對元素結合起來,用插入的逗號鏈接它們。我沒必要考慮三個函數調用中參數的名稱是什麼,因此我可使用方便的Scala快捷方式,也就是說,使用_跳過名稱。reduce()函數從前兩個元素入手,將它們結合成一個元素,成爲下一個串接中的第一個元素。在「瀏覽」列表的同時,reduce()構建了所需的逗號分隔的字符串。

我首先展現Scala實現是由於我對它的語法比較熟悉,並且Scala分別爲篩選、變換和轉化概念使用了行業通用的名稱,即filter、map和reduce。

Groovy實現

Groovy擁有相同的功能,但對它們進行命名的方式與腳本語言(好比Ruby)更加一致。清單1中處理示例的Groovy版本如清單3所示。

清單3.Groovy處理

class TheCompanyProcess {

public static String cleanUpNames(List listOfNames) {

listOfNames

.findAll {it.length() > 1}

.collect {it.capitalize()}

.join(',')

}

}

儘管清單3在結構上相似於清單2中的Scala示例,但方法名稱不一樣。Groovy的findAll集合方法應用所提供的代碼塊,保留代碼塊爲true的元素。如同Scala,Groovy包含一個隱式參數機制,爲單參數代碼塊使用預約義的it隱式參數。collect方法(Groovy的map版本)對集合的每一個元素執行所提供的代碼塊。Groovy提供一個函數(join()),使用所提供的分隔符將字符串集合串聯爲單一字符串,這正是本示例中所須要的。

Clojure實現

Clojure是一個使用reduce、map和filter函數名的函數式語言,如清單4所示。

清單4.Clojure處理示例

(defn process [list-of-emps]

(reduce str (interpose ","

(map clojure.string/capitalize

(filter #(< 1 (count %)) list-of-emps)))))

Clojure的thread-first宏

thread-last宏使集合的處理變得更加簡單。相似的Clojure宏thread-first可簡化與JavaAPI的交互。例如廣泛的Java代碼語句person.getInformation().

getAddress().getPostalCode(),這體現了Java違反迪米特法則的傾向。這種類型的語句給Clojure編程帶來一些煩惱,迫使使用JavaAPI的開發人員不得不構建由內而外的語句,好比(getPostalCode(getAddress(getInformationperson)))。thread-first宏消除了這一語法困擾。您可使用宏將嵌套調用編寫爲(->persongetInformationgetAddressgetPostalCode),想嵌套多少層均可以。

若是您不習慣查看Clojure,可使用清單4中的代碼,其結構可能不夠清晰。Clojure這樣的Lisp是「由內而外」進行工做的,因此必須從最後的參數值list-of-emps着手。Clojure的(filter)函數接受兩個參數:用於進行篩選的函數(本例中爲匿名函數)和要篩選的集合。

您能夠爲第一個參數編寫一個正式函數定義,好比(fn[x](<1(countx))),但使用Clojure能夠更簡潔地編寫匿名函數。與前面的示例同樣,篩選操做的結果是一個較少的集合。(map)函數將變換函數接受爲第一個參數,將集合(本例中是(filter)操做的返回值)做爲第二個參數。Clojure的(map)函數的第一個參數一般是開發人員提供的函數,但接受單一參數的任何函數都有效;內置capitalize函數也符合要求。

最後,(map)操做的結果成爲了(reduce)的集合參數。(reduce)的第一個參數是組合函數(應用於(interpose)的返回的(str))。(interpose)在集合的每一個元素之間(除了最後一個)插入其第一個參數。

當函數嵌套過多時,即便最有經驗的開發人員也會倍感頭疼,如清單4中的(process)函數所示。所幸的是,Clojure包含的宏支持您將結構「調整」爲更可讀的順序。清單5中的功能與清單4中的功能同樣。

清單5.使用Clojure的thread-last宏

(defn process2 [list-of-emps]

(->> list-of-emps

(filter #(< 1 (count %)))

(map clojure.string/capitalize)

(interpose ",")

(reduce str)))

Clojurethread-last宏採起對集合應用各類變換的常見操做並顛倒典型的Lisp的順序,恢復了從左到右的更天然的閱讀方式。在清單5中,首先是(list-of-emps)集合。代碼塊中每一個隨後的表單被應用於前一個表單。Lisp的優點之一在於其語法靈活性:任什麼時候候代碼的可讀性變得不好時,您均可以將代碼調整回具備較高可讀性。

函數式編程的優點

在一篇標題爲「BeatingtheAverages」的著名文章中,PaulGraham定義了BlubParadox:他「編造」了一種名爲Blub的虛假語言,而且考慮在其餘語言與Blub之間進行功能比較:

只要咱們假想的Blub程序員往下看一連串功能,他就知道本身是在往下看。不如Blub功能強大的語言顯然不怎麼強大,由於它們缺乏程序員習慣使用的一些功能。但當咱們假想的Blub程序員從另外一個方向,也就是說,往上看一連串功能時,他並無意識到本身在往上看。他看到的只不過是怪異的語言。他可能認爲它們在功能上與Blub幾近相同,只是多了其餘難以理解的東西。Blub對他而言已經足夠好,由於他是在Blub環境中能夠思考問題。

對於不少Java開發人員而言,清單2中的代碼看起來陌生而又奇怪,所以難以將它看做是有優點的代碼。但當您中止過於細化任務執行細節時,就釋放了愈來愈智能的語言和運行時的潛能,從而作出了強大的改進。例如,JVM的到來(解除了開發人員的內存管理困擾)爲先進垃圾回收的建立開闢了全新的研發領域。使用命令式編碼時,您深陷於迭代循環的細節,難以進行並行性等優化。從更高的層面思考操做(好比filter、map和reduce)可將概念與實現分離開來,將並行性等修改從一項複雜、詳細的任務轉變爲一個簡單的API更改。

想想如何將清單1中的代碼變爲多線程代碼。因爲您密切參與了for循環期間發生的細節,因此您還必須處理煩人的併發代碼。而後思考一下清單6所示的Scala並行版本。

清單6.實現進程並行性

val parallelResult = employees

.par

.filter(f => f.length() > 1)

.map(f => f.capitalize)

.reduce(_ + "," + _)

清單2與清單6之間唯一的差異在於,將.par方法添加到了命令流中。.par方法返回後續操做依據的集合的並行版本。因爲我將對集合的操做指定爲高階概念,因此底層運行時能夠自由地完成更多的工做。

面向命令式對象的開發人員每每會考慮使用重用類,由於他們的語言鼓勵將類做爲構建塊。函數編程語言傾向於重用函數。函數式語言構建複雜的通用功能(好比filter()、map()和reduce())並經過做爲參數提供的函數來實現定製。在函數式語言中,將數據結構轉換爲列表和映射等標準集合是很尋常的事,由於它們接着就能夠被強大的內置函數所操控。

例如,在Java環境中存在許多XML處理框架,每一個框架都封裝本身的私有版本的XML結構,並經過本身的方法交付它。在Clojure這樣的語言中,XML被轉換爲基於映射的標準數據結構,該結構對已經存在於語言中的強大的變換、約簡和篩選操做開放。

全部現代語言都包含或添加了函數式編程結構,使函數式編程成爲將來開發中不可或缺的一部分。Java下一代語言都實現了強大的函數式功能,有時使用不一樣的名稱和行爲。在本期中,我介紹了Scala、Groovy和Clojure中的一種新編碼風格並展現了一些優點。

小猿圈Java講師提醒你們:天天學習一點技術問題,只要功夫深,鐵杵磨成針,學習不是一朝一夕的,是須要付出行動的,並且還要堅持小猿圈java自學交流羣:743849624,學習新的技術須要不斷的查閱資料,看視頻,複習,練習,若是你工做中或者生活中遇到什麼問題,能夠到小猿圈去尋找答案的,相信會給你滿意的答覆的。

相關文章
相關標籤/搜索