最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會(huì)員登陸 & 注冊

effective java 3 - 第7章 lambda和stream[45] 謹(jǐn)慎使用Stream

2023-02-20 19:24 作者:CC挑燈夜讀_谷  | 我要投稿

????在Java8中增加了Stream API,簡化了串行或并行的大批量操作。這個(gè)API 提供了兩個(gè)關(guān)鍵抽象:Stream(流)代表數(shù)據(jù)元素有限或無限的順序,Stream pipeline(流管道)則代表這些元素的一個(gè)多級(jí)計(jì)算。Stream 中的元素可能來自任何位置。常見的來源包括集合、數(shù)組、文件、正則表達(dá)式模式匹配器、偽隨機(jī)數(shù)生成器,以及其他Stream。Stream 中的數(shù)據(jù)元素可以是對象引用,或者基本類型值。它支持三種基本類型:int、long和double。

????一個(gè)Stream pipeline中包含一個(gè)源Stream,接著是0個(gè)或多個(gè)中間操作(intermediate operation)和一個(gè)終止操作(terminal operation)。每個(gè)中間操作都會(huì)通過某種方式對Stream進(jìn)行轉(zhuǎn)換,例如將每個(gè)元素映射到該元素的函數(shù),或者過濾掉不滿足某些條件的所有元素。所有的中間操作都是將一個(gè)Stream 轉(zhuǎn)換成另一個(gè)Stream,其元素類型可能與輸入的Stream一樣,也可能不同。終止操作會(huì)在最后一個(gè)中間操作產(chǎn)生的Stream上執(zhí)行一個(gè)最終的計(jì)算,例如將其元素保存到一個(gè)集合中,并返回某一個(gè)元素,或者打印出所有元素等。

????Stream pipeline 通常是lazy 的:直到調(diào)用終止操作時(shí)才會(huì)開始計(jì)算,對于完成終止操作不需要的數(shù)據(jù)元素,將永遠(yuǎn)不會(huì)被計(jì)算。正是這種lazy 計(jì)算,使無限Stream 成為可能。注意,沒有終止操作的Stream pipeline 將成為一個(gè)靜默的無操作指令,因此千萬不能忘記終止操作。

????Stream API 是流式(fluent)的:所有包含pipeline的調(diào)用可以鏈接成一個(gè)表達(dá)式。事實(shí)上,多個(gè)pipeline 也可以鏈接在一起,成為一個(gè)表達(dá)式。

????在默認(rèn)情況下,Stream pipeline 是按順序運(yùn)行得。要使pipeline并發(fā)執(zhí)行,只需在該pipeline的任何Stream 上調(diào)用 parallel 方法即可,但是通常不建議這么做(詳見第48條)。

????Stream API 包羅萬象,足以用Stream 執(zhí)行任何計(jì)算,但是“可以”并不意味著應(yīng)該。如果使用得當(dāng),Stream 可以使程序變得更加簡潔、清晰;如果使用不當(dāng),會(huì)使程序變得混亂且難以維護(hù)。對于什么時(shí)候應(yīng)該使用Stream,并沒有硬性的規(guī)定,但是可以有所啟發(fā)。

????以下面的程序?yàn)槔淖饔檬菑脑~典文件中讀取單詞,并打印出單詞長度符合用戶指定的最低值的所有換位詞。記住,包含相同的字母,但是字母順序不同的兩個(gè)詞,稱作換位詞(anagram)。該程序會(huì)從用戶指定的詞典文件中讀取每一個(gè)詞,并將符合條件的單詞放入一個(gè)映射中。這個(gè)映射是按字母順序排列的單詞,因此,“staple”的鍵是 “aelpst”,“petals”的鍵也是“aelpst”:這兩個(gè)詞就是換位詞,所有換位詞的字母排列形式是一樣的(有時(shí)候也叫alphagram)。映射值是包含了字母排列形式一致的所有單詞。詞典讀取完成之后,每一個(gè)列表就是一個(gè)完整的換位詞組。隨后,程序會(huì)遍歷映射的values(), 預(yù)覽并打印出單詞長度符合極限值的所有列表。?

????這個(gè)程序中有一個(gè)步驟值得注意(?groups.computeIfAbsent ...),這是使用了Java8中新增的computeIfAbsent方法。這個(gè)方法會(huì)在映射中查找一個(gè)鍵:如果這個(gè)鍵存在,該方法只會(huì)返回與之關(guān)聯(lián)的值。如果鍵不存在,該方法就會(huì)對該鍵運(yùn)用指定的函數(shù)對象算出一個(gè)值,將這個(gè)值與鍵關(guān)聯(lián)起來,并返回計(jì)算得到的值。computeIfAbsent 方法簡化了將多個(gè)值與每個(gè)鍵關(guān)聯(lián)起來的映射實(shí)現(xiàn)。

????下面舉個(gè)例子,它也能解決上述問題,只不過大量使用了Stream。注意,它的所有程序都是包含在一個(gè)表達(dá)式中,除了打開詞典文件的那部分代碼之外。之所以咬在另一個(gè)表達(dá)式中打開詞典文件,只是為了使用try-with-resources語句,它可以確保關(guān)閉詞典文件:

????如果你發(fā)現(xiàn)這段代碼好難懂,別擔(dān)心,你并不是唯一有此想法的人。它雖然簡潔,但是難以讀懂,對于那些使用Stream 還不熟練的程序員而言更是如此。濫用Stream 會(huì)使程序代碼更難以讀懂和維護(hù)。

????好在還有一種舒適的中間方案。下面的程序解決了同樣的問題,它使用了Stream,但是沒有過度使用。結(jié)果,與原來的程序相比,這個(gè)版本變得既簡短又清晰:

????即使你之前沒怎么接觸過Stream,這段程序也不難理解。它在try-with-resources塊中打開詞典文件,獲得一個(gè)包含了文件中所有代碼的Stream。Stream 變量命名為words,是建議Stream中的每個(gè)元素均為單詞。這個(gè)Stream中的pipeline沒有中間操作;它的終止操作是將所有的單詞集合到一個(gè)映射中,按照它們的字母排序形式對單詞進(jìn)行分組(詳見第46條)。這個(gè)映射與前面兩個(gè)版本中的是完全相同的。隨后,在映射的values視圖中打開了一個(gè)新的Stream<List<String>>。當(dāng)然,這個(gè)Stream 中的元素都是換位詞分組。Stream進(jìn)行了過濾,把所有單詞長度小于minGroupSize的單詞都去掉了,最后,通過終止操作的forEach打印剩下的分組。

????注意,Lambda參數(shù)的名稱都是經(jīng)過精心挑選的。實(shí)際上參數(shù)應(yīng)當(dāng)以group命名,只是這樣得到的代碼行對于書本而言太寬了。在沒有顯式類型的情況下,仔細(xì)命名 Lambda參數(shù),這對于Stream pipeline 的可讀性至關(guān)重要。

????還要注意單詞的字母排序是在一個(gè)單獨(dú)的alphabetize 方法中完成的。給操作命名,并且不要在主程序中保留實(shí)現(xiàn)細(xì)節(jié),這些都增強(qiáng)了程序的可讀性。在Stream pipeline中使用 helper方法,對于可讀性而言,比在迭代化代碼中使用更為重要,因?yàn)閜ipeline 缺乏顯式的類型信息和具體臨時(shí)變量。

????可以重新實(shí)現(xiàn)alphabetize 方法來使用Stream,只是基于Stream 的alphabetize方法沒那么清晰,難以正確編寫,速度也可能變慢。這些不足是因?yàn)镴ava不支持基本類型的char Stream(這并不意味著Java不應(yīng)該支持 char Stream;也不可能支持)。為了證明用Stream 處理char 值的各種危險(xiǎn),請看以下代碼

????"Hello world!".chars().forEach(System.out::println);

????或許你以為它會(huì)輸出 Hello world!,但是運(yùn)行之后發(fā)現(xiàn),它輸出的是721011081081113211911111410810033。這是因?yàn)?#34;Hello world!".chars()返回的Stream中的元素并不是char值,而是int 值,因此調(diào)用了print的int覆蓋。名為chars的方法卻返回int值的Stream,這固然會(huì)造成困擾。修正方法利用強(qiáng)轉(zhuǎn)調(diào)用正確的覆蓋:

"Hello world!".chars().forEach(x -> System.out.print((char) x));

但是,最好避免利用Stream 來處理char值

????剛開始使用Stream 時(shí),可能會(huì)沖動(dòng)到很不得將所有的循環(huán)都轉(zhuǎn)換成Stream,但是切記,千萬別沖動(dòng)。這可能會(huì)破壞代碼的可讀性和易維護(hù)性。一般來說,即使是相當(dāng)復(fù)雜的任務(wù),最好也結(jié)合Stream和迭代一起完成,如上面的 Anagrams 程序范例所示。因此,重構(gòu)現(xiàn)有代碼來使用Stream,并且只在必要的時(shí)候才在新代碼中使用。

????如本條目中的范例程序所示,Stream pipeline利用函數(shù)對象(一般是Lambda或者方法引用)來描述重復(fù)的計(jì)算,而迭代版代碼則利用現(xiàn)有代碼塊來描述重復(fù)的計(jì)算。下列工作只能通過代碼塊,而不能通過函數(shù)對象來完成:

  • 從代碼塊中,可以讀取或修改范圍內(nèi)的任意局部變量;從Lambda則只能讀取final 或者有效的final 變量[JLS 4.12.4], 并且不能修改任何local變量。

  • 從代碼塊中,可以從外圍方法中return 、break或continue 外圍循環(huán),或者拋出該方法聲明要拋出的任何受檢異常;從Lambda 中則完全無法完成這些事情。

如果某個(gè)計(jì)算最好要利用上述這些方法要描述,它可能并不太適合Stream。反之,Stream 可以使得完成這些工作變得易如反掌:

  • 統(tǒng)一轉(zhuǎn)換元素的序列

  • 過濾元素的序列

  • 利用單個(gè)操作(如添加、連接或者計(jì)算其最小值)合并元素的順序

  • 將元素的序列存放到一個(gè)集合中,比如根據(jù)某些公共屬性進(jìn)行分組

  • 搜索滿足某些條件的元素的序列

如果某個(gè)計(jì)算最好是利用這些方法來完成,它就非常適合使用Stream。

????利用Stream 很難完成的一件事情就是,同時(shí)從一個(gè)pipeline 的多個(gè)階段去訪問相應(yīng)的元素:一旦將一個(gè)值映射到某個(gè)其他值,原來的值就丟失了。一種解決辦法是將每個(gè)值都映射到包含原始值和新值的一個(gè)對象對(pair object),不過這并非萬全之策,當(dāng)pipeline的多個(gè)階段都需要這些對象對時(shí)尤其如此。這樣得到的代碼將是混亂、繁雜的,違背了Stream 的初衷。最好的解決辦法是,當(dāng)需要訪問較早階段的值時(shí),將映射顛倒過來。

????例如,編寫一個(gè)打印出前20個(gè)梅森素?cái)?shù)(Mersenne primes)的程序。解釋一下,梅森素?cái)?shù)是一個(gè)形式為2^n -1的數(shù)字。如果 p是一個(gè)素?cái)?shù),相應(yīng)的梅森數(shù)字也是素?cái)?shù);那么它就是一個(gè)梅森素?cái)?shù)。作為pipeline 的第一個(gè)Stream,我們想要的是所有素?cái)?shù)。下面的方法將返回(無限)Stream。假設(shè)使用的是靜態(tài)導(dǎo)入,便于訪問BigInteger的靜態(tài)成員:

????方法的名稱(primes)是一個(gè)復(fù)數(shù)名詞,它描述了Stream的元素。強(qiáng)烈建議返回Stream的所有方法都采用這種命名慣例,因?yàn)榭梢栽鰪?qiáng)Stream pipeline的可讀性。該方法使用靜態(tài)工廠Stream.iterator,它有兩個(gè)參數(shù):Stream的第一個(gè)元素,以及從前一個(gè)元素種生成下一個(gè)元素的一個(gè)函數(shù)。下面的程序用于打印出前20個(gè)梅森素?cái)?shù)。

????這段程序是對上述內(nèi)容的簡單編碼示范:它從素?cái)?shù)開始,計(jì)算出相應(yīng)的梅森素?cái)?shù),過濾掉所有不是素?cái)?shù)的數(shù)字(其中50是個(gè)神奇的數(shù)字,它控制著這個(gè)概率素性測試),限制最終得到的Stream為20個(gè)元素,并打印出來。

????現(xiàn)在假設(shè)想要在每個(gè)梅森素?cái)?shù)之前加上其指數(shù)(p)。這個(gè)值只出現(xiàn)在第一個(gè)Stream 中,因此在負(fù)責(zé)輸出結(jié)果的終止操作中是訪問不到的。所幸將發(fā)生在第一個(gè)中間操作中的映射顛倒過來,便可以很容易地計(jì)算出梅森數(shù)字的指數(shù)。該指數(shù)只不過是一個(gè)以二進(jìn)制表示的位數(shù),因此終止操作可以產(chǎn)生所要的結(jié)果:

????

????現(xiàn)實(shí)中有許多任務(wù)并不明確要使用Stream 還是迭代。例如有個(gè)任務(wù)是要將一副新紙牌初始化。假設(shè)Card是一個(gè)不變值類,

effective java 3 - 第7章 lambda和stream[45] 謹(jǐn)慎使用Stream的評(píng)論 (共 條)

分享到微博請遵守國家法律
达尔| 昌吉市| 攀枝花市| 南岸区| 濉溪县| 金平| 东台市| 嘉祥县| 新干县| 新密市| 五河县| 清徐县| 永康市| 射洪县| 千阳县| 封丘县| 兴宁市| 穆棱市| 石城县| 佛冈县| 三门峡市| 和田市| 宁德市| 徐闻县| 固阳县| 武陟县| 仪征市| 上杭县| 黔江区| 丹寨县| 定结县| 错那县| 成武县| 揭西县| 元朗区| 仁怀市| 宝应县| 泌阳县| 大埔县| 陆川县| 图们市|