對于java大家都已經不陌生了吧,今天小猿圈Java講師就分享一篇關于java函數式編碼結構及優勢的知識點,希望對于學習java的你有一定的幫助,想學習就需要積累。

探讨三種下一代JVM語言:Groovy、Scala和Clojure,比較并對比新的功能和範例,讓Java開發人員對自己近期的未來發展有大體的認識。
當垃圾回收成為主流時,它消除了所有類别的難以調試的問題,使運作時能夠為開發人員管理複雜的、容易出錯的程序。函數式程式設計旨在為您編寫的算法實作同樣的優化,這樣您就可以從一個更高的抽象層面開展工作,同時運作時執行複雜的優化。
Java下一代語言并不都占用從指令式到函數式的語言頻譜的同一位置,但都展現出函數功能和習語。函數式程式設計技術有明确定義,但語言有時為相同的函數式概念使用不同的術語,使得我們很難看到相似之處。在本期文章中,我比較了Scala、Groovy和Clojure的函數式編碼風格并讨論了它們的優勢。
指令式處理
我要首先探讨一個常見問題及其指令式解決方案。假如給定一個名稱清單,其中一些名稱包含一個字元。系統會要求您在一個逗号分隔的字元串中傳回名稱,該字元串中不包含單字母的名稱,每個名稱的首字母都大寫。實作該算法的Java代碼如清單1所示。
清單1.指令式處理
public class TheCompanyProcess {
public String cleanNames(List<String> listOfNames) {
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講師提醒大家:每天學習一點技術問題,隻要功夫深,鐵杵磨成針,學習不是一朝一夕的,是需要付出行動的,而且還要堅持小猿圈,學習新的技術需要不斷的查閱資料,看視訊,複習,練習,如果你工作中或者生活中遇到什麼問題,可以到小猿圈去尋找答案的,相信會給你滿意的答複的。