Java 8 vs. Scala(二):Stream vs. Collection

【編者按】在之前文章中拯辙,我們介紹了 Java 8和Scala的Lambda表達式對比灵迫。在本文,將進行 Hussachai Puripunpinyo Java 和 Scala 對比三部曲的第二部分聘裁,主要關注 Stream 和 Collection赴叹,本文由 OneAPM 工程師編譯整理。

首先兵扬,為大家做一個簡短的介紹麻裳,collection 是有限的數(shù)據(jù)集,而 stream 是數(shù)據(jù)的序列集器钟,可以是有限的或無限的津坑。

Streams API 是 Java 8 中新發(fā)布的 API,主要用于操作 collection 和 streaming 數(shù)據(jù)傲霸。Collections API 會改變數(shù)據(jù)集狀態(tài)疆瑰,而 Streams API 則不會。例如昙啄,當你調(diào)用Collections.sort(list)時穆役,該方法會對傳入的參數(shù)進行排序,而調(diào)用list.stream().sorted() 則會復制一份數(shù)據(jù)進行操作梳凛,保持原數(shù)據(jù)不變耿币。你可以在這里獲得更多關于 API 數(shù)據(jù)流的信息

以下是筆者從 Java 8 文檔中摘出的 collections 和 streams 之間的比較。強烈建議大家閱讀 完整版韧拒。

Streams 和 collections 有以下幾點區(qū)別:

  1. 無存儲淹接。steam 不是存儲數(shù)據(jù)元素的數(shù)據(jù)結構。而是通過計算操作管道從源頭傳輸數(shù)據(jù)元素叛溢。

2.本質(zhì)是函數(shù)塑悼。對 Stream 對象操作能得到一個結果,但是不會修改原始數(shù)據(jù)楷掉。

  1. Laziness-seeking(延遲搜索):Stream 的很多操作如 filter厢蒜、map、sort 和 duplicate removal(去重)可以延遲實現(xiàn)靖诗,意思是我們只要檢查到滿足要求的元素就可以返回郭怪。

  2. 可能是不受限制的:Streams 允許 Client 取足夠多的元素直到滿足某個條件為止支示。而 Collections 不能這么做刊橘。

  3. 消耗的。Steam 中的元素在 steam 生存期內(nèi)只能被訪問一次颂鸿。

Java 和 Scala 都可以很簡單地同時計算 collection 中的值促绵。在 Java 中,你只需調(diào)用parallelStream()* 或者 stream().parallel(),而不是stream()败晴。在 Scala 中浓冒,在調(diào)用其他方法之前,必須先調(diào)用 par()函數(shù)尖坤。而且可以通過添加 parallelism 來提高程序的性能稳懒。不幸的是,大多數(shù)時間它的執(zhí)行速度都非常慢慢味。事實上场梆,parallelism 是一個很容易被誤用的功能。 點這閱讀這有趣的文章

在 JavaDoc 中纯路, parallelStream()方法的介紹是:可能返回一個并行的 stream(collection作為數(shù)據(jù)源)或油,所以它也可能返回一個串行 stream。( 有人做過關于該API的研究

圖像標題

Java 的 Stream API 是延后執(zhí)行的驰唬。這意味著顶岸,沒有指定一個終結操作(比如 collect() 方法調(diào)用),那么所有的中間調(diào)用(比如 filter 調(diào)用)是不會被執(zhí)行的叫编。延遲的流處理主要是為了優(yōu)化 stream API 的執(zhí)行效率辖佣。比如對一個數(shù)據(jù)流進行過濾、映射以及求和運算宵溅,通過使用延后機制凌简,那么所有操作只要遍歷一次,從而減少中間調(diào)用恃逻。同時雏搂,延后執(zhí)行允許每個操作只處理必要的數(shù)據(jù)。相反寇损,Scala 的 collections 是即時處理的凸郑。這樣是否意味著,在測試中矛市,Java Stream API始終優(yōu)于 Scala 芙沥?如果只比較 Java 的 Stream API 和 Scala的 Collection API,那么Java Stream API 的確優(yōu)于 Scala Collection API浊吏。但在 Scala 中有更多的選擇而昨。通過簡單地調(diào)用toStream(),就可以將一個 Collection 轉(zhuǎn)換成一個 Stream找田,或者可以使用 view (一種提供延后處理能力的 Collection)來處理數(shù)據(jù)集合歌憨。

下面粗略介紹下 Scala 的 Stream 和 View 特性

Scala 的 Stream

Scala 的 Stream 和 Java 的有所不同。在 Scala Stream 中墩衙,無需調(diào)用終結操作去取得Stream 的結果务嫡。Stream 是一個繼承 Abstractseq甲抖、 LinearseqGenericTraversableTemplate trait的抽象類。所以心铃,你可以把Stream當作 SEQ准谚。

如果你不熟悉 Scala,可以將 Seq 當作 Java 里的 List去扣。(Scala 中的 List 不是一個接口)柱衔。

這里需知道 Streams 中的元素都是延遲計算的,正因為此愉棱,Stream能夠計算無限數(shù)據(jù)流秀存。如果要計算集合中的所有元素,Stream 和 List 有相同的性能羽氮。一旦計算出結果或链,數(shù)值將被緩存。 Stream 有一個 force 函數(shù)档押,能夠強制評估 stream 再返回結果澳盐。注意,不要在無限流中調(diào)用該函數(shù)令宿,也不要強制該 API 處理整個 stream 的操作叼耙,比如 size()、tolist()粒没、foreach() 等筛婉,這些操作在 Scala 的 Stream 中都是隱式的。

在 Scala Stream 中實現(xiàn) Fibonacci 數(shù)列癞松。

def fibFrom(a: Int, b: Int): Stream[Int] = a #:: fibFrom(b, a + b)
val fib1 = fibFrom(0, 1) //0 1 1 2 3 5 8 …
val fib5 = fibFrom(0, 5) //0 5 5 10 15 …
//fib1.force //Don’t do this cause it will call the function infinitely and soon you will get the OutOfMemoryError
//fib1.size //Don’t do this too with the same reason as above.
fib1.take(10) //Do this. It will take the first 10 from the inifite Stream.
fib1.take(20).foreach(println(_)) //Prints 20 first numbers

::是 collection 中常用的連接數(shù)據(jù)的方法爽撒。而 #::表示是連接數(shù)據(jù)但是是延遲執(zhí)行的(Scala中的方法名都很隨意)。

Scala 的 View

再次重申响蓉,Scala 的 collection 是一個嚴格 collection硕勿,而 view 是非嚴格的。View 是基于一個基礎 collection 的 collection枫甲,其中所有的轉(zhuǎn)換都會延遲執(zhí)行源武。通過調(diào)用 view 函數(shù)可以將嚴格 collection 轉(zhuǎn)換成 view,也可以通過調(diào)用 force 方法轉(zhuǎn)換回來想幻。View 并不緩存結果粱栖,每次調(diào)用時才會執(zhí)行轉(zhuǎn)換。就像數(shù)據(jù)庫的 View脏毯,但它是虛擬 collection闹究。

創(chuàng)建一個數(shù)據(jù)集。

public class Pet {
    public static enum Type {
        CAT, DOG
    }
    public static enum Color {
        BLACK, WHITE, BROWN, GREEN
    }
    private String name;
    private Type type;
    private LocalDate birthdate;
    private Color color;
    private int weight;
    ...
}

假設有一個寵物集抄沮,接下來會利用該集合詳細說明跋核。

過濾器

要求:從集合過濾一只胖乎乎的寵物,胖乎乎的定義是體重超過 50 磅叛买,還想得到一個在 2013年1月1日出生的寵物名單砂代。下面的代碼片段顯示了如何以不同的方式實現(xiàn)該濾波器的工作。

Java 方法1:傳統(tǒng)方式

//Before Java 8
List<Pet> tmpList = new ArrayList<>();
for(Pet pet: pets){
    if(pet.getBirthdate().isBefore(LocalDate.of(2013, Month.JANUARY, 1))
            && pet.getWeight() > 50){
        tmpList.add(pet);
    }
}

這種方式在命令式語言中十分常見率挣。首先刻伊,必須創(chuàng)建一個臨時集合,然后遍歷所有元素椒功,存儲滿足條件的元素到臨時集中捶箱。的確有點繞口,但其結果和效率都非常不錯动漾。但本人不得不掃興地說丁屎,傳統(tǒng)方法比 Streams API 更快。不過旱眯,完全不用擔心性能問題晨川,因為代碼的簡潔比輕微的性能增益更重要。

Java 方法2:Streams API

//Java 8 - Stream
pets.stream()
    .filter(pet -> pet.getBirthdate().isBefore(LocalDate.of(2013,   Month.JANUARY, 1)))
    .filter(pet -> pet.getWeight() > 50)
    .collect(toList())

以上代碼表示删豺,使用 Streams API 過濾集合中的元素共虑。之所以故意兩次調(diào)用過濾函數(shù),是想表明 Streams 的 API 設計就像一個 Builder pattern呀页。在 Builder pattern 調(diào)用構建方法之前妈拌,可以將各種方法串聯(lián)起來。在 Streams API 中蓬蝶,構建方法被稱為終結操作尘分,非終結操作的叫做中間操作。終結操作可能不同于構造函數(shù)丸氛,因為它在 Streams API 中只能被調(diào)用一次音诫。但還有很多可使用的終結操作,比如 collect雪位、count竭钝、min、max雹洗、iterator香罐、toArray。這些操作會產(chǎn)生結果时肿,而終端操作會消耗值庇茫,例如 forEach。那么螃成,你認為傳統(tǒng)方法和 Streams API 哪一個的可讀性更強旦签?

Java 方法3:Collections API

//Java 8 - Collection
pets.removeIf(pet -> !(pet.getBirthdate().isBefore(LocalDate.of(2013,Month.JANUARY, 1))
                && pet.getWeight() > 50));
//Applying De-Morgan's law.
pets.removeIf(pet -> pets.get(0).getBirthdate().toEpochDay() >= LocalDate.of(2013, Month.JANUARY, 1).toEpochDay()
                || pet.getWeight() <= 50);

這種方法是最簡短的查坪。但是,它修改了原始集合宁炫,而前面的方法不會偿曙。removeif 函數(shù)將Predicate<T>(函數(shù)接口)作為參數(shù)。Predicate 是一個行為參數(shù)羔巢,它只有一個名為 test 抽象方法望忆,只需要一個對象并返回布爾值。注意竿秆,這里必須使用“启摄!”取反,或者可以應用 De Morgan 定理幽钢,使得代碼看起來像二次聲明歉备。

Scala 方法:Collection、View和Stream

//Scala - strict collection
pets.filter { pet => pet.getBirthdate.isBefore(LocalDate.of(2013, Month.JANUARY, 1))}
.filter { pet => pet.getWeight > 50 } //List[Pet]
//Scala - non-strict collection
pets.views.filter { pet => pet.getBirthdate.isBefore(LocalDate.of(2013, Month.JANUARY, 1))}
.filter { pet => pet.getWeight > 50 } //SeqView[Pet]
//Scala - stream
pets.toStream.filter { pet => pet.getBirthdate.isBefore(LocalDate.of(2013,  Month.JANUARY, 1))}
.filter { pet => pet.getWeight > 50 } //Stream[Pet]

Scala 的解決方案類似于 Java 的 Streams API匪燕。但首先威创,必須調(diào)用 view 函數(shù)把嚴格集轉(zhuǎn)向非嚴格集,然后再用 tostream 函數(shù)把嚴格集轉(zhuǎn)成一個 stream谎懦。

接下來直接上代碼肚豺。

分組

通過元素的一個屬性對起所在集合做 group。結果是 Map<T, List<T>>界拦,其中T是一個泛型類型吸申。

要求:通過類型對寵物分組,諸如狗享甸,貓等等截碴。

Java 8 vs. Scala(二):Stream vs. Collection

注意:groupingBy 是 java.util.stream.Collectors 的靜態(tài)的 helper method。

排序

根據(jù)屬性對集合中的元素排序蛉威。結果會是任何類型的集合日丹,根據(jù)配置來維持元素順序。

要求:需按照類型蚯嫌、名字和顏色排序哲虾。

Java 8 vs. Scala(二):Stream vs. Collection

映射

將給定函數(shù)應用在集合元素中。根據(jù)定義的函數(shù)不同择示,其返回的結果類型也不同束凑。

要求:需將寵物轉(zhuǎn)化成字符串,以%s?—?name: %s, color: %s的格式栅盲。

Java 8 vs. Scala(二):Stream vs. Collection

尋找第一個

返回第一個能與指定 predicate 匹配的值汪诉。

要求:找一個名為Handsome的寵物。無論有多少個Handsome谈秫,只取第一個扒寄。

這個問題有點棘手鱼鼓。不知道你是否注意,在 Scala 中筆者所使用的是 find 函數(shù)而不是 filter 该编?如果用 filter 代替 find迄本,它就會計算集合中所有元素,因為 scala collection 是嚴格的上渴。但是,在 Java 的 Streams API 中你可以放心使用 filter喜颁,因為它會計算需要的第一個值稠氮,并不會計算所有元素。這就是延遲執(zhí)行的好處!

接下來半开,向大家介紹 scala 中更多集合延遲執(zhí)行的實例隔披。我們假定 filter 總是返回 true,然后再取第二個值寂拆。將會是什么結果呢奢米?

pets.filter { x => println(x.getName); true }.get(1) --- (1)
pets.toStream.filter { x => println(x.getName); true }.get(1) -- (2)

如上所示,(1)式將會打印出集合中所有寵物的名字纠永,而(2)式則只輸出前2個寵物的名字鬓长。這就是 lazy collection 的好處,總是延遲計算尝江。

pets.view.filter { x => println(x.getName); true }.get(1) --- (3)

(3)式和(2)式會有一樣的結果嗎涉波?錯!它的結果和(1)是一樣的炭序,你知道為什么嗎啤覆?

通過比較 Java 和 Scala 中的一些共同的操作方法 ——filter、group惭聂、map 和 find窗声;很明顯 Scala 的方法比 Java 更簡潔。你更喜歡哪一個呢?哪一個的可讀性更強辜纲?

在文章的下一個部分笨觅,我們將比較哪種方式更快。敬請期待耕腾!

原文鏈接: https://dzone.com/articles/java-8-vs-scalapart-ii-streams-api

OneAPM for Java 能夠深入到所有 Java 應用內(nèi)部完成應用性能管理和監(jiān)控屋摇,包括代碼級別性能問題的可見性、性能瓶頸的快速識別與追溯幽邓、真實用戶體驗監(jiān)控炮温、服務器監(jiān)控和端到端的應用性能管理。想閱讀更多技術文章牵舵,請訪問 OneAPM 官方博客柒啤。

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末丘跌,一起剝皮案震驚了整個濱河市减噪,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖偷办,帶你破解...
    沈念sama閱讀 211,194評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異玉凯,居然都是意外死亡固蚤,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評論 2 385
  • 文/潘曉璐 我一進店門拳话,熙熙樓的掌柜王于貴愁眉苦臉地迎上來先匪,“玉大人,你說我怎么就攤上這事弃衍⊙椒牵” “怎么了?”我有些...
    開封第一講書人閱讀 156,780評論 0 346
  • 文/不壞的土叔 我叫張陵镜盯,是天一觀的道長岸裙。 經(jīng)常有香客問我,道長速缆,這世上最難降的妖魔是什么降允? 我笑而不...
    開封第一講書人閱讀 56,388評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮艺糜,結果婚禮上拟糕,老公的妹妹穿的比我還像新娘。我一直安慰自己倦踢,他們只是感情好送滞,可當我...
    茶點故事閱讀 65,430評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著辱挥,像睡著了一般犁嗅。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上晤碘,一...
    開封第一講書人閱讀 49,764評論 1 290
  • 那天褂微,我揣著相機與錄音,去河邊找鬼园爷。 笑死宠蚂,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的童社。 我是一名探鬼主播求厕,決...
    沈念sama閱讀 38,907評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了呀癣?” 一聲冷哼從身側響起美浦,我...
    開封第一講書人閱讀 37,679評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎项栏,沒想到半個月后浦辨,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,122評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡沼沈,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,459評論 2 325
  • 正文 我和宋清朗相戀三年流酬,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片列另。...
    茶點故事閱讀 38,605評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡芽腾,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出访递,到底是詐尸還是另有隱情晦嵌,我是刑警寧澤同辣,帶...
    沈念sama閱讀 34,270評論 4 329
  • 正文 年R本政府宣布拷姿,位于F島的核電站,受9級特大地震影響旱函,放射性物質(zhì)發(fā)生泄漏响巢。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,867評論 3 312
  • 文/蒙蒙 一棒妨、第九天 我趴在偏房一處隱蔽的房頂上張望踪古。 院中可真熱鬧,春花似錦券腔、人聲如沸伏穆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽枕扫。三九已至,卻和暖如春辱魁,著一層夾襖步出監(jiān)牢的瞬間烟瞧,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評論 1 265
  • 我被黑心中介騙來泰國打工染簇, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留参滴,地道東北人。 一個月前我還...
    沈念sama閱讀 46,297評論 2 360
  • 正文 我出身青樓锻弓,卻偏偏與公主長得像砾赔,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,472評論 2 348

推薦閱讀更多精彩內(nèi)容