好程式設計師Java培訓分享Java函式式編碼結構

好程式設計師發表於2020-10-12

  好程式設計師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 的優勢之一在於其語法靈活性:任何時候程式碼的可讀性變得很差時,你都可以將程式碼調整回具有較高可讀性。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69913864/viewspace-2726372/,如需轉載,請註明出處,否則將追究法律責任。

相關文章