學(xué)習(xí)不同 Java.net 語(yǔ)言中類似的函數(shù)結(jié)構(gòu)
前言
函數(shù)式編程語(yǔ)言包含多個(gè)系列的常見(jiàn)函數(shù)。但開(kāi)發(fā)人員有時(shí)很難在語(yǔ)言之間進(jìn)行切換,因?yàn)槭煜さ暮瘮?shù)具有不熟悉的名稱。函數(shù)式語(yǔ)言傾向于基于函數(shù)范例來(lái)命名這些常見(jiàn)函數(shù)。從腳本背景衍生而來(lái)的語(yǔ)言傾向于使用更具描述性的名稱(有時(shí)是多個(gè)名稱,包含多個(gè)指向同一個(gè)函數(shù)的別名)。
在本期文章中,我將繼續(xù)探討 3 種重要函數(shù)(過(guò)濾、映射和縮減)的實(shí)用性,展示來(lái)自每種 Java 下一代語(yǔ)言的實(shí)現(xiàn)細(xì)節(jié)。文中的討論和示例旨在減輕 3 種語(yǔ)言對(duì)類似函數(shù)結(jié)構(gòu)使用的不一致名稱時(shí)可能引起的混淆。
過(guò)濾
在過(guò)濾 函數(shù)中,您可指定一個(gè)布爾值條件(通常為一個(gè)高階函數(shù)的形式),將它應(yīng)用到一個(gè)集合。該函數(shù)返回集合的子集,其中的元素與該條件匹配。過(guò)濾與查找 函數(shù)緊密相關(guān),后者返回集合中第一個(gè)匹配的元素。
Scala
Scala 擁有多個(gè)過(guò)濾函數(shù)變體。最簡(jiǎn)單的情形基于傳遞的條件來(lái)過(guò)濾某個(gè)列表。在第一個(gè)示例中,我創(chuàng)建一個(gè)數(shù)字列表。然后使用了 filter() 函數(shù),并傳遞了一個(gè)代碼塊,指定了所有元素都可以被 3 整除的條件:
val numbers = List.range(1, 11) numbers filter (x => x % 3 == 0) // List(3, 6, 9)
我可依靠隱式的參數(shù)來(lái)創(chuàng)建該代碼快的更加簡(jiǎn)潔的版本:
numbers filter (_ % 3 == 0) // List(3, 6, 9)
第二個(gè)版本不那么冗長(zhǎng),因?yàn)樵?Scala 中,您可以將參數(shù)替換為下劃線。兩個(gè)版本都可以得到相同的結(jié)果。
過(guò)濾操作的許多示例都使用了數(shù)字,但 filter() 適用于任何集合。此示例將 filter() 應(yīng)用到一個(gè)單詞列表來(lái)確定 3 字母單詞:
val words = List("the", "quick", "brown", "fox", "jumped", "over", "the", "lazy", "dog") words filter (_.length == 3) // List(the, fox, the, dog)
Scala 中的另一個(gè)過(guò)濾函數(shù)變體是 partition() 函數(shù),它將一個(gè)集合拆分為多個(gè)部分。這種拆分基于您傳遞的高階函數(shù)來(lái)確定分離條件。在這里,partition() 函數(shù)將返回兩個(gè)列表,它們依據(jù)哪些列表成員可被 3 整除來(lái)進(jìn)行拆分:
numbers partition (_ % 3 == 0) // (List(3, 6, 9),List(1, 2, 4, 5, 7, 8, 10))
filter() 函數(shù)返回一個(gè)匹配元素集合,而 find() 僅返回第一個(gè)匹配元素:
numbers find (_ % 3 == 0) // Some(3)
但是,find() 的返回值不是匹配的值本身,而是一個(gè)包裝在 Option 類中的值。Option 有兩個(gè)可能的值:Some 或 None。像其他一些函數(shù)式語(yǔ)言一樣,Scala 使用 Option 作為一種約定來(lái)避免在缺少某個(gè)值時(shí)返回 null。Some() 實(shí)例包裝實(shí)際的返回值,在 numbers find (_ % 3 == 0) 的情況下,該值為 3。如果我嘗試查找某個(gè)不存在的值,那么返回值將為 None:
numbers find (_ < 0) // None
Scala 還包含多個(gè)函數(shù),它們基于一個(gè)判定函數(shù)來(lái)處理一個(gè)集合并返回值或丟棄它們。takeWhile() 函數(shù)返回集合中滿足判定函數(shù)的最大的值集:
List(1, 2, 3, -4, 5, 6, 7, 8, 9, 10) takeWhile (_ > 0) // List(1, 2, 3)
dropWhile() 函數(shù)跳過(guò)滿足判定條件的最大元素?cái)?shù)量:
words dropWhile (_ startsWith "t") // List(quick, brown, fox, jumped, over, the, lazy, dog)
Groovy
Groovy 不是一個(gè)函數(shù)式語(yǔ)言,但它包含許多函數(shù)范例,一些范例的名稱源自腳本語(yǔ)言。例如,在函數(shù)式語(yǔ)言中,該函數(shù)在傳統(tǒng)上被稱為 filter() 的函數(shù),就是 Groovy 中的 findAll() 方法:
(1..10).findAll {it % 3 == 0} // [3, 6, 9]
像 Scala 的過(guò)濾函數(shù)一樣,Groovy 可處理所有類型,包括字符串:
def words = ["the", "quick", "brown", "fox", "jumped", "over", "the", "lazy", "dog"] words.findAll {it.length() == 3} // [The, fox, the, dog]
Groovy 還有一個(gè)類似 partition() 的函數(shù),稱為 split():
(1..10).split {it % 3} // [[1, 2, 4, 5, 7, 8, 10], [3, 6, 9]]
split() 方法的返回值是一個(gè)嵌套數(shù)組,就像 Scala 中從 partition() 返回的嵌套列表。
Groovy 的 find() 方法返回集合中第一個(gè)匹配的元素:
(1..10).find {it % 3 == 0} // 3
不同于 Scala,Groovy 遵循 Java 約定,在 find() 未能找到元素時(shí)返回 null:
(1..10).find {it < 0} // null
Groovy 還擁有 takeWhile() 和 dropWhile() 方法,它們具有與 Scala 的版本類似的語(yǔ)義:
[1, 2, 3, -4, 5, 6, 7, 8, 9, 10].takeWhile {it > 0} // [1, 2, 3] words.dropWhile {it.startsWith("t")} // [quick, brown, fox, jumped, over, the, lazy, dog]
與 Scala 示例中一樣,dropWhile 被用作一個(gè)專門(mén)的過(guò)濾器:它丟棄與判定條件匹配的最大前綴,僅過(guò)濾列表的第一部分:
def moreWords = ["the", "two", "ton"] + words moreWords.dropWhile {it.startsWith("t")} // [quick, brown, fox, jumped, over, the, lazy, dog]
Clojure
Clojure 擁有令人震驚的集合操作例程數(shù)量。由于 Clojure 的動(dòng)態(tài)類型,其中許多例程都是通用的。許多開(kāi)發(fā)人員傾向于使用 Clojure,因?yàn)樗募蠋?kù)非常豐富和靈活。Clojure 使用傳統(tǒng)的函數(shù)式編程名稱,如 (filter ) 函數(shù)所示:
(def numbers (range 1 11)) (filter (fn [x] (= 0 (rem x 3))) numbers) ; (3 6 9)
像其他語(yǔ)言一樣,Clojure 為簡(jiǎn)單的匿名函數(shù)提供了簡(jiǎn)潔的語(yǔ)法:
(filter #(zero? (rem % 3)) numbers) ; (3 6 9)
而且與其他語(yǔ)言中一樣,Clojure 的函數(shù)適用于任何適用的類型,比如字符串:
(def words ["the" "quick" "brown" "fox" "jumped" "over" "the" "lazy" "dog"]) (filter #(= 3 (count %)) words) ; (the fox the dog)
Clojure 的 (filter ) 返回類型為 Seq,它通過(guò)圓括號(hào)來(lái)描述。Seq 是 Clojure 中的順序集合的核心抽象。
映射
所有 Java 下一代語(yǔ)言中常見(jiàn)的第二個(gè)主要的函數(shù)變形是映射。映射函數(shù)接受一個(gè)高階函數(shù)和一個(gè)集合,然后向每個(gè)元素應(yīng)用傳遞的函數(shù)并返回一個(gè)集合。返回的集合(不同于過(guò)濾)的大小與原始集合相同,但更新了值。
Scala
Scala 的 map() 函數(shù)接受一個(gè)代碼塊并返回轉(zhuǎn)換的集合:
List(1, 2, 3, 4, 5) map (_ + 1) // List(2, 3, 4, 5, 6)
map() 函數(shù)適用于所有適用的類型,但它不一定返回集合元素的已轉(zhuǎn)換集合。在此示例中,我在一個(gè)字符串中返回所有元素的大小列表:
words map (_.length) // List(3, 5, 5, 3, 6, 4, 3, 4, 3)
在函數(shù)式編程語(yǔ)言中常常會(huì)產(chǎn)生嵌套列表,以至于嵌套列表對(duì)解除嵌套(通常稱為扁平化)的庫(kù)支持很常見(jiàn)。以下是扁平化一個(gè)嵌套列表的示例:
List(List(1, 2, 3), List(4, 5, 6), List(7, 8, 9)) flatMap (_.toList) // List(1, 2, 3, 4, 5, 6, 7, 8, 9)
獲得的 List 中僅包含元素,刪除了額外的基礎(chǔ)架構(gòu)。flatMap 函數(shù)也適用于可能未以傳統(tǒng)方式嵌套的數(shù)據(jù)結(jié)構(gòu)。例如,您可將一個(gè)字符串視為一個(gè)嵌套字符系列:
words flatMap (_.toList) // List(t, h, e, q, u, i, c, k, b, r, o, w, n, f, o, x, ...
Groovy
Groovy 還包含多個(gè)稱為 collect() 的映射變體。默認(rèn)的變體接受一個(gè)代碼塊,以便將該變體應(yīng)用到集合的每個(gè)元素:
(1..5).collect {it += 1} // [2, 3, 4, 5, 6]
像其他語(yǔ)言一樣,Groovy 允許對(duì)簡(jiǎn)單的匿名高階函數(shù)使用簡(jiǎn)寫(xiě);it 保留字用于替代單獨(dú)的參數(shù)。
collect() 方法適用于您可向其提供合理的判定條件的任何集合,比如一個(gè)字符串列表:
def words = ["the", "quick", "brown", "fox", "jumped", "over", "the", "lazy", "dog"] words.collect {it.length()} // [3, 5, 5, 3, 6, 4, 3, 4, 3]
Groovy 還有一個(gè)類似于 flatMap() 的折疊內(nèi)部結(jié)構(gòu)的方法,稱為 flatten():
[[1, 2, 3], [4, 5, 6], [7, 8, 9]].flatten() // [1, 2, 3, 4, 5, 6, 7, 8, 9]
flatten() 方法也適用于不太明顯的集合,比如字符串:
(words.collect {it.toList()}).flatten() // [t, h, e, q, u, i, c, k, b, r, o, w, n, f, o, x, j, ...
Clojure
Clojure 包含一個(gè) (map ) 函數(shù),它接受一個(gè)高階函數(shù)(其中包含運(yùn)算符)和一個(gè)集合:
(map inc numbers) ; (2 3 4 5 6 7 8 9 10 11)
(map ) 的第一個(gè)參數(shù)可以是任何接受單個(gè)參數(shù)的函數(shù):命名函數(shù)、匿名函數(shù)或已存在的函數(shù),比如遞增其參數(shù)的 inc。此示例中演示了更典型的匿名語(yǔ)法,它生成一個(gè)字符串中的單詞長(zhǎng)度的集合:
(map #(count %) words) ; (3 5 5 3 6 4 3 4 3)
Clojure 的 (flatten ) 函數(shù)類似于 Groovy 的:
(flatten [[1 2 3] [4 5 6] [7 8 9]]) ; (1 2 3 4 5 6 7 8 9)
折疊/縮減
在 3 種 Java 下一代語(yǔ)言中,第三個(gè)常見(jiàn)函數(shù)在名稱上擁有最多變體和許多細(xì)微的區(qū)別。foldLeft 和 reduce 是一個(gè)名為 catamorphism 的列表操作概念上的特定變體,該概念是列表折疊的一種泛化。在此示例中,“折疊左側(cè)” 表示:
使用一個(gè)二進(jìn)制函數(shù)或運(yùn)算符將列表的第一個(gè)元素與第二個(gè)元素相結(jié)合,創(chuàng)建一個(gè)新的第一個(gè)元素。
重復(fù)第一步,直到列表用完且您得到一個(gè)單一元素。
請(qǐng)注意,這是您在對(duì)一組數(shù)字求和時(shí)所做的操作:從 0 開(kāi)始,加第一個(gè)元素,將結(jié)果與第二個(gè)元素相加,一直執(zhí)行此操作,直到列表元素被用完為止。
Scala
Scala 擁有最豐富的折疊運(yùn)算集合,這是因?yàn)樗谝欢ǔ潭壬虾?jiǎn)化了動(dòng)態(tài)類型的 Groovy 和 Clojure 中沒(méi)有的多種類型場(chǎng)景??s減函數(shù)常用于執(zhí)行求和:
List.range(1, 10) reduceLeft((a, b) => a + b) // 45
提供給 reduce() 的函數(shù)通常是一個(gè)接受兩個(gè)參數(shù),并返回單個(gè)結(jié)果的函數(shù)或運(yùn)算符,以便可以使用一個(gè)列表。您可以使用 Scala 的語(yǔ)法糖來(lái)縮短函數(shù)定義:
List.range(1, 10).reduceLeft(0)(_ + _) // 45
reduceLeft() 函數(shù)假設(shè)第一個(gè)元素是運(yùn)算的左側(cè)。對(duì)于相加等運(yùn)算符,操作數(shù)的位置無(wú)關(guān)緊要,但放置順序?qū)ο喑冗\(yùn)算至關(guān)重要。如果希望反轉(zhuǎn)運(yùn)算符應(yīng)用的順序,可以使用 reduceRight():
List.range(1, 10) reduceRight(_ - _) // 5
了解何時(shí)可使用縮減等高級(jí)抽象是掌握函數(shù)編程的一個(gè)關(guān)鍵。此示例使用 reduceLeft() 來(lái)確定集合中最常的單詞:
words.reduceLeft((a, b) => if (a.length > b.length) a else b) // jumped
縮減和折疊運(yùn)算擁有重疊的功能,它們具有細(xì)微的差別,但這不屬于本文的討論范圍。但是,通??梢钥吹剿鼈兊囊粋€(gè)明顯區(qū)別。在 Scala 中,簽名 reduceLeft[B >:A](op:(B, A) => B):B 表明惟一想要的參數(shù)就是組合元素的函數(shù)。初始值應(yīng)該是集合中的第一個(gè)值。相對(duì)而言,簽名 foldLeft[B](z:B)(op:(B, A) => B):B 表示結(jié)果的一個(gè)初始種子值,所以您可以返回與列表元素類型不同的類型。
以下是一個(gè)使用 foldLeft 對(duì)集合求和的示例:
List.range(1, 10).foldLeft(0)(_ + _) // 45
Scala 支持運(yùn)算符重疊,所以兩個(gè)常見(jiàn)的折疊操作 foldLeft 和 foldRight 分別擁有相應(yīng)的運(yùn)算符:/: 和 :\。因此,您可以使用 foldLeft 創(chuàng)建 sum 的簡(jiǎn)潔版本:
(0 /: List.range(1, 10)) (_ + _) // 45
類似地,要找到一個(gè)列表中每個(gè)元素的級(jí)聯(lián)區(qū)別(求和運(yùn)算的反向操作,無(wú)可否認(rèn)這種需求很少見(jiàn)),您可以使用 foldRight() 函數(shù)或 :\ 運(yùn)算符:
(List.range(1, 10) :\ 0) (_ - _) // 5
Groovy
Groovy 通過(guò)使用重疊來(lái)支持與 Scala 的 reduce() 和 foldLeft() 選項(xiàng)相同的功能,從而進(jìn)入縮減類別。該函數(shù)的一個(gè)版本接受一個(gè)初始值。此示例使用 inject() 方法生成一個(gè)集合的總和:
(1..10).inject {a, b -> a + b} // 55
替代形式接受一個(gè)初始值:
(1..10).inject(0, {a, b -> a + b}) // 55
Groovy 擁有一個(gè)比 Scala 或 Clojure 小得多的函數(shù)庫(kù) — Groovy 是一種不強(qiáng)調(diào)函數(shù)式編程的多范例編程,看到這種情況毫不奇怪。
Clojure
Clojure 主要是一種函數(shù)式編程語(yǔ)言,所以它支持 (reduce )。(reduce ) 函數(shù)接受一個(gè)可選的初始值,以便同時(shí)涵蓋 Scala 所處理的 reduce() 和 foldLeft() 情形。(reduce ) 函數(shù)沒(méi)有給用戶帶來(lái)任何驚喜。它接受一個(gè)需要兩個(gè)參數(shù)的函數(shù)和一個(gè)集合:
(reduce + (range 1 11)) ; 55
Clojure 將對(duì)類似 reduce 的功能的高級(jí)支持包含在一個(gè)名為 reducers 的庫(kù)中,后面的一期文章將會(huì)介紹這個(gè)庫(kù)。
結(jié)束語(yǔ)
學(xué)習(xí)不同范例(比如函數(shù)式編程)的部分挑戰(zhàn)在于學(xué)習(xí)新術(shù)語(yǔ)。在不同社區(qū)使用不同的詞匯時(shí),這一過(guò)程會(huì)變得更加復(fù)雜。但一旦掌握了相似性,您就會(huì)看到,所有 3 種 Java 下一代語(yǔ)言都以令人驚奇的方式在語(yǔ)法上提供了重疊的功能。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- Java客戶端調(diào)用.NET的WebService實(shí)例
- java利用java.net.URLConnection發(fā)送HTTP請(qǐng)求的方法詳解
- java.net.MalformedURLException異常的解決方法
- Android 中出現(xiàn)java.net.BindException: bind failed: EADDRINUSE 問(wèn)題解決辦法
- java.net.ConnectException: Connection refused問(wèn)題解決辦法
- 簡(jiǎn)單了解java函數(shù)式編碼結(jié)構(gòu)及優(yōu)勢(shì)
- 不同Java泛型構(gòu)造函數(shù)的詳解
相關(guān)文章
解決引用slf4j中Logger.info只打印出文字沒(méi)有數(shù)據(jù)的問(wèn)題
這篇文章主要介紹了解決引用slf4j中Logger.info只打印出文字沒(méi)有數(shù)據(jù)的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12SpringBoot+Redis實(shí)現(xiàn)數(shù)據(jù)字典的方法
這篇文章主要介紹了SpringBoot+Redis實(shí)現(xiàn)數(shù)據(jù)字典的方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-10-10springboot自定義配置及自定義對(duì)象映射的全流程
這篇文章主要介紹了springboot自定義配置及自定義對(duì)象映射的全流程,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-10-10idea64.exe.vmoptions文件如何設(shè)置調(diào)整VM配置文件
這篇文章主要介紹了idea64.exe.vmoptions文件如何設(shè)置調(diào)整VM配置文件問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-04-04Java基于SpringBoot和tk.mybatis實(shí)現(xiàn)事務(wù)讀寫(xiě)分離代碼實(shí)例
這篇文章主要介紹了Java基于SpringBoot和tk.mybatis實(shí)現(xiàn)事務(wù)讀寫(xiě)分離代碼實(shí)例,讀寫(xiě)分離,基本的原理是讓主數(shù)據(jù)庫(kù)處理事務(wù)性增、改、刪操作,而從數(shù)據(jù)庫(kù)處理SELECT查詢操作,數(shù)據(jù)庫(kù)復(fù)制被用來(lái)把事務(wù)性操作導(dǎo)致的變更同步到集群中的從數(shù)據(jù)庫(kù),需要的朋友可以參考下2023-10-10Java實(shí)現(xiàn)簡(jiǎn)單雙色球搖獎(jiǎng)功能過(guò)程解析
這篇文章主要介紹了Java實(shí)現(xiàn)簡(jiǎn)單雙色球搖獎(jiǎng)功能過(guò)程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-09-09JsonFormat與@DateTimeFormat注解實(shí)例解析
這篇文章主要介紹了JsonFormat與@DateTimeFormat注解實(shí)例解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-12-12