1 代碼比較
具有類型的項目名稱列表
生成隨機列表
2 性能比較
迭代元素
并行流優化
3 局限性
條件循環
重復
4 概括
Java8的發布是Java歷史上的一個重大時刻。Streams 和 Lambda 被引入,它們現在被廣泛使用。如果你不知道 Streams,或者從來沒有聽說過它,那是完全沒有問題的。在大多數情況下,循環同樣可以滿足我們的需要,沒有 Streams 也不會遇到任何問題。
那我們為什么需要Streams?它們能取代循環嗎?或者比循環更有優勢?在本文中,我們將研究代碼,比較性能,并看看Streams作為循環的替代品有多好。
1 代碼比較
Streams 增加了代碼復雜性,因為它們需要類、接口和導入新的包;相比之下,循環天生就是內置的,不需要額外的引入任何東西。這在某些點上是對的,但也不盡然:代碼復雜度并不能僅僅看引入幾個類、幾個包文件來衡量,更重要的是代碼的可讀性。讓我們看一些例子。
具有類型的項目名稱列表
假設我們有一個項目列表,并且想要特定項目類型的名稱列表。使用循環,我們需要編寫以下內容:
ListgetItemNamesOfType(List - items,Item.Typetype){ List
itemNames=newArrayList<>(); for(Itemitem:items){ if(item.type()==type){ itemNames.add(item.name()); } } returnitemNames; }
閱讀代碼,我們會發現 ArrayList 應該實例化一個 new ,并且add()應該在每個循環中進行類型檢查和調用。再來看,Streams 版本是如何處理的:
ListgetItemNamesOfTypeStream(List - items,Item.Typetype){ returnitems.stream() .filter(item->item.type()==type) .map(item->item.name()) .toList(); }
在 Lambda 的幫助下,可以立即發現我們首先選擇具有給定類型的項目,然后獲取過濾項目的名稱列表。在這種代碼中,逐行流程與邏輯流程非常一致。
生成隨機列表
讓我們看另一個例子,在時間比較部分,我們將回顧關鍵的 Streams 方法并將它們的執行時間與循環進行比較。為此,我們需要一個隨機的Items 列表。這是一個帶有靜態方法的片段,它給出了 隨機 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)]); } }
現在,讓我們Item使用循環創建一個隨機列表。代碼如下所示:
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()方法中值是不可修改的,為我們提供了不變性,因此我們可以在代碼中的任何位置共享它,而不必擔心副作用。這使得代碼不易出錯,并且讀者更容易理解我們的代碼。
Streams 提供了多種有用的方法,讓我們可以編寫簡潔的代碼。最流行的是:
allMatch()
anyMatch()
count()
filter()
findFirst()
forEach()
map()
reduce()
sorted()
limit()
2 性能比較
在正常情況下,Streams 的行為類似于循環,對執行時間影響很小或沒有影響。讓我們將 Streams 中的一些主要行為與循環實現進行比較。
迭代元素
當我們有一個元素集合時,在很多情況下都會迭代集合中的所有元素。在 Streams 中,諸如forEach()、map()、reduce()和 filter()類的方法可以執行這種全元素迭代。
讓我們考慮一種情況,我們想要對列表中的每種類型的項目進行計數。
帶 for 循環的代碼如下所示:
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)); }
它們看起來截然不同,但它們的表現如何呢?下表是 100 次嘗試的平均執行時間:
正如我們在上面的比較表中看到的,Streams 和循環在迭代整個列表時顯示出很小的執行時間差異。在大多數情況下,這對于其他 Stream 方法(如map()、forEach()、reduce()等)是相同的。
并行流優化
因此,我們發現在迭代列表時,流的性能并不比循環更好或更差。然而,Streams 有一個循環所不具備的神奇之處:我們可以輕松地利用流進行多線程計算。 所要做的就是使用parallelStream()而不是stream()。
為了了解我們可以從中獲得多少影響,讓我們看一下下面的示例,其中我們模擬了耗時較長的任務,如下所示:
privatevoidlongTask(){ //Mocklongtask. try{ Thread.sleep(1); }catch(InterruptedExceptione){ thrownewRuntimeException(e); } }
循環遍歷列表將如下所示:
protectedvoidloop(List- items){ for(Itemitem:items){ longTask(); } }
Stream將如下所示:
protectedvoidstream(List- items){ items.stream().forEach(item->longTask()); }
最后,并行流將如下所示:
protectedvoidparallel(List- items){ items.parallelStream().forEach(item->longTask()); }
請注意, onlystream()已更改為parallelStream()。
這是比較:
正如預期的那樣,循環和Stream幾乎沒有什么區別。那么并行流呢?聳人聽聞!與其他實現相比,它節省了 80% 以上的執行時間!這怎么可能?
對于需要很長時間才能完成并且應該為列表中的每個元素獨立完成的任務,它們可以同時運行,我們可以期待顯著的改進。這就是并行流正在做的事情。他們將它們分配到多個線程中并使它們同時運行。
并行流并非萬能通用,只有當任務是獨立的時,它才有用。如果任務不是獨立的,并且必須共享相同的資源,則必須使用鎖(主要是Java中的synchronized關鍵字)來保證它們的安全,此時它們的運行速度慢于正常的迭代。
3 局限性
然而,Stream也有局限性。一種情況是條件循環,另一種情況是重復。讓我們看看它們的意思。
條件循環
當我們想要重復直到條件為真但不確定需要多少次迭代時,我們通常使用while循環。
booleancondition=true; while(condition){ ... condition=doSomething(); }
使用 Streams 表現相同的代碼如下所示:
Stream.iterate(true,condition->condition,condition->doSomething()) .forEach(unused->...);
我們可以看到 Streams 代碼部分會干擾讀取,例如condition -> condition檢查條件是否為真,unused以及forEach()。考慮到這一點,條件循環最好寫在while循環中。
重復
重復是for循環存在的主要原因之一。假設我們想重復這個過程十次。有了for循環,就可以很容易地寫成:
for(inti=0;i10;?i++)?{ ??... }
在 Streams 中,實現此目的的一種方法是創建IntStream包含[0, 1, 2, ... , 9]并迭代它的 。
IntStream.range(0,10).forEach(i->...);
雖然代碼可能看起來簡潔而正確,但它看起來更側重于0到10(排除)范圍內的值,其中for循環代碼可以重復讀取十次,因為更常見的做法是這樣寫repeat:從0開始,以重復次數結束。
4 概括
我們已經對流和循環進行了一些比較。那么……Streams 可以取代循環嗎?嗯,一如既往,這取決于情況!然而,Streams 通常可以為我們提供更簡潔、易于閱讀的代碼和優化。
來源:betterprogramming.pub/can-streams-replace-loops-in-java-f56d4461743a
審核編輯:劉清
-
JAVA
+關注
關注
19文章
2958瀏覽量
104548 -
for循環
+關注
關注
0文章
61瀏覽量
2493
原文標題:Java 中的 Stream 可以替代 for 循環嗎?
文章出處:【微信號:芋道源碼,微信公眾號:芋道源碼】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論