1 代碼比較
具有類型的項(xiàng)目名稱列表
生成隨機(jī)列表
2 性能比較
迭代元素
并行流優(yōu)化
3 局限性
條件循環(huán)
重復(fù)
4 概括
Java8的發(fā)布是Java歷史上的一個(gè)重大時(shí)刻。Streams 和 Lambda 被引入,它們現(xiàn)在被廣泛使用。如果你不知道 Streams,或者從來(lái)沒(méi)有聽(tīng)說(shuō)過(guò)它,那是完全沒(méi)有問(wèn)題的。在大多數(shù)情況下,循環(huán)同樣可以滿足我們的需要,沒(méi)有 Streams 也不會(huì)遇到任何問(wèn)題。
那我們?yōu)槭裁葱枰猄treams?它們能取代循環(huán)嗎?或者比循環(huán)更有優(yōu)勢(shì)?在本文中,我們將研究代碼,比較性能,并看看Streams作為循環(huán)的替代品有多好。
1 代碼比較
Streams 增加了代碼復(fù)雜性,因?yàn)樗鼈冃枰悺?a target="_blank">接口和導(dǎo)入新的包;相比之下,循環(huán)天生就是內(nèi)置的,不需要額外的引入任何東西。這在某些點(diǎn)上是對(duì)的,但也不盡然:代碼復(fù)雜度并不能僅僅看引入幾個(gè)類、幾個(gè)包文件來(lái)衡量,更重要的是代碼的可讀性。讓我們看一些例子。
具有類型的項(xiàng)目名稱列表
假設(shè)我們有一個(gè)項(xiàng)目列表,并且想要特定項(xiàng)目類型的名稱列表。使用循環(huán),我們需要編寫(xiě)以下內(nèi)容:
ListgetItemNamesOfType(List - items,Item.Typetype){ List
itemNames=newArrayList<>(); for(Itemitem:items){ if(item.type()==type){ itemNames.add(item.name()); } } returnitemNames; }
閱讀代碼,我們會(huì)發(fā)現(xiàn) ArrayList 應(yīng)該實(shí)例化一個(gè) new ,并且add()應(yīng)該在每個(gè)循環(huán)中進(jìn)行類型檢查和調(diào)用。再來(lái)看,Streams 版本是如何處理的:
ListgetItemNamesOfTypeStream(List - items,Item.Typetype){ returnitems.stream() .filter(item->item.type()==type) .map(item->item.name()) .toList(); }
在 Lambda 的幫助下,可以立即發(fā)現(xiàn)我們首先選擇具有給定類型的項(xiàng)目,然后獲取過(guò)濾項(xiàng)目的名稱列表。在這種代碼中,逐行流程與邏輯流程非常一致。
生成隨機(jī)列表
讓我們看另一個(gè)例子,在時(shí)間比較部分,我們將回顧關(guān)鍵的 Streams 方法并將它們的執(zhí)行時(shí)間與循環(huán)進(jìn)行比較。為此,我們需要一個(gè)隨機(jī)的Items 列表。這是一個(gè)帶有靜態(tài)方法的片段,它給出了 隨機(jī) Item:
publicrecordItem(Typetype,Stringname){
publicenumType{
WEAPON,ARMOR,HELMET,GLOVES,BOOTS,
}
privatestaticfinalRandomrandom=newRandom();
privatestaticfinalString[]NAMES={
"beginner",
"knight",
"king",
"dragon",
};
publicstaticItemrandom(){
returnnewItem(
Type.values()[random.nextInt(Type.values().length)],
NAMES[random.nextInt(NAMES.length)]);
}
}
現(xiàn)在,讓我們Item使用循環(huán)創(chuàng)建一個(gè)隨機(jī)列表。代碼如下所示:
List- items=newArrayList<>(100); for(inti=0;i100;?i++)?{ ????items.add(Item.random()); }
Streams 的代碼如下所示:
List- items=Stream.generate(Item::random).limit(length).toList();
這是一段精彩且易于閱讀的代碼。此外,List返回的toList()方法中值是不可修改的,為我們提供了不變性,因此我們可以在代碼中的任何位置共享它,而不必?fù)?dān)心副作用。這使得代碼不易出錯(cuò),并且讀者更容易理解我們的代碼。
Streams 提供了多種有用的方法,讓我們可以編寫(xiě)簡(jiǎn)潔的代碼。最流行的是:
allMatch()
anyMatch()
count()
filter()
findFirst()
forEach()
map()
reduce()
sorted()
limit()
2 性能比較
在正常情況下,Streams 的行為類似于循環(huán),對(duì)執(zhí)行時(shí)間影響很小或沒(méi)有影響。讓我們將 Streams 中的一些主要行為與循環(huán)實(shí)現(xiàn)進(jìn)行比較。
迭代元素
當(dāng)我們有一個(gè)元素集合時(shí),在很多情況下都會(huì)迭代集合中的所有元素。在 Streams 中,諸如forEach()、map()、reduce()和 filter()類的方法可以執(zhí)行這種全元素迭代。
讓我們考慮一種情況,我們想要對(duì)列表中的每種類型的項(xiàng)目進(jìn)行計(jì)數(shù)。
帶 for 循環(huán)的代碼如下所示:
publicMaploop(List - items){ Map
map=newHashMap<>(); for(Itemitem:items){ map.compute(item.type(),(key,value)->{ if(value==null)return1; returnvalue+1; }); } returnmap; }
Streams 的代碼如下所示:
publicMapstream(List - items){ returnitems.stream().collect(Collectors.toMap( Item::type, value->1, Integer::sum)); }
它們看起來(lái)截然不同,但它們的表現(xiàn)如何呢?下表是 100 次嘗試的平均執(zhí)行時(shí)間:

正如我們?cè)谏厦娴谋容^表中看到的,Streams 和循環(huán)在迭代整個(gè)列表時(shí)顯示出很小的執(zhí)行時(shí)間差異。在大多數(shù)情況下,這對(duì)于其他 Stream 方法(如map()、forEach()、reduce()等)是相同的。
并行流優(yōu)化
因此,我們發(fā)現(xiàn)在迭代列表時(shí),流的性能并不比循環(huán)更好或更差。然而,Streams 有一個(gè)循環(huán)所不具備的神奇之處:我們可以輕松地利用流進(jìn)行多線程計(jì)算。 所要做的就是使用parallelStream()而不是stream()。
為了了解我們可以從中獲得多少影響,讓我們看一下下面的示例,其中我們模擬了耗時(shí)較長(zhǎng)的任務(wù),如下所示:
privatevoidlongTask(){
//Mocklongtask.
try{
Thread.sleep(1);
}catch(InterruptedExceptione){
thrownewRuntimeException(e);
}
}
循環(huán)遍歷列表將如下所示:
protectedvoidloop(List- items){ for(Itemitem:items){ longTask(); } }
Stream將如下所示:
protectedvoidstream(List- items){ items.stream().forEach(item->longTask()); }
最后,并行流將如下所示:
protectedvoidparallel(List- items){ items.parallelStream().forEach(item->longTask()); }
請(qǐng)注意, onlystream()已更改為parallelStream()。
這是比較:

正如預(yù)期的那樣,循環(huán)和Stream幾乎沒(méi)有什么區(qū)別。那么并行流呢?聳人聽(tīng)聞!與其他實(shí)現(xiàn)相比,它節(jié)省了 80% 以上的執(zhí)行時(shí)間!這怎么可能?
對(duì)于需要很長(zhǎng)時(shí)間才能完成并且應(yīng)該為列表中的每個(gè)元素獨(dú)立完成的任務(wù),它們可以同時(shí)運(yùn)行,我們可以期待顯著的改進(jìn)。這就是并行流正在做的事情。他們將它們分配到多個(gè)線程中并使它們同時(shí)運(yùn)行。
并行流并非萬(wàn)能通用,只有當(dāng)任務(wù)是獨(dú)立的時(shí),它才有用。如果任務(wù)不是獨(dú)立的,并且必須共享相同的資源,則必須使用鎖(主要是Java中的synchronized關(guān)鍵字)來(lái)保證它們的安全,此時(shí)它們的運(yùn)行速度慢于正常的迭代。
3 局限性
然而,Stream也有局限性。一種情況是條件循環(huán),另一種情況是重復(fù)。讓我們看看它們的意思。
條件循環(huán)
當(dāng)我們想要重復(fù)直到條件為真但不確定需要多少次迭代時(shí),我們通常使用while循環(huán)。
booleancondition=true;
while(condition){
...
condition=doSomething();
}
使用 Streams 表現(xiàn)相同的代碼如下所示:
Stream.iterate(true,condition->condition,condition->doSomething()) .forEach(unused->...);
我們可以看到 Streams 代碼部分會(huì)干擾讀取,例如condition -> condition檢查條件是否為真,unused以及forEach()。考慮到這一點(diǎn),條件循環(huán)最好寫(xiě)在while循環(huán)中。
重復(fù)
重復(fù)是for循環(huán)存在的主要原因之一。假設(shè)我們想重復(fù)這個(gè)過(guò)程十次。有了for循環(huán),就可以很容易地寫(xiě)成:
for(inti=0;i10;?i++)?{
??...
}
在 Streams 中,實(shí)現(xiàn)此目的的一種方法是創(chuàng)建IntStream包含[0, 1, 2, ... , 9]并迭代它的 。
IntStream.range(0,10).forEach(i->...);
雖然代碼可能看起來(lái)簡(jiǎn)潔而正確,但它看起來(lái)更側(cè)重于0到10(排除)范圍內(nèi)的值,其中for循環(huán)代碼可以重復(fù)讀取十次,因?yàn)楦R?jiàn)的做法是這樣寫(xiě)repeat:從0開(kāi)始,以重復(fù)次數(shù)結(jié)束。
4 概括
我們已經(jīng)對(duì)流和循環(huán)進(jìn)行了一些比較。那么……Streams 可以取代循環(huán)嗎?嗯,一如既往,這取決于情況!然而,Streams 通常可以為我們提供更簡(jiǎn)潔、易于閱讀的代碼和優(yōu)化。
來(lái)源:betterprogramming.pub/can-streams-replace-loops-in-java-f56d4461743a
審核編輯:劉清
-
JAVA
+關(guān)注
關(guān)注
20文章
3001瀏覽量
116419 -
for循環(huán)
+關(guān)注
關(guān)注
0文章
61瀏覽量
2884
原文標(biāo)題:Java 中的 Stream 可以替代 for 循環(huán)嗎?
文章出處:【微信號(hào):芋道源碼,微信公眾號(hào):芋道源碼】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
怎么看待CKS32單片機(jī)能取代ST32單片機(jī)問(wèn)題
為什么CAN能取代RS485?
Java Map的幾種循環(huán)方式學(xué)習(xí)總結(jié)
對(duì)講機(jī)和微信的區(qū)別在哪里?為什么微信,手機(jī)不能取代對(duì)講機(jī)?
AI不能取代醫(yī)生的原因分析
人工智能取代職業(yè)排行
Java教程之如何進(jìn)行Java中的do-while循環(huán)
Java的循環(huán)語(yǔ)句的詳細(xì)資料說(shuō)明
人工智能取代醫(yī)生?
設(shè)計(jì)應(yīng)用中的循環(huán)網(wǎng)絡(luò)布局
機(jī)器人目前能取代人類記者和編輯嗎?
詳解Python中的while循環(huán)
集成壓控振蕩器的寬帶鎖相環(huán)能取代分立式解決方案嗎
為什么需要Streams?它們能取代Java中的for循環(huán)嗎?
評(píng)論