为什么不应该用Stream forEach替换for循环的3个原因
太棒了! 我們正在將代碼庫遷移到Java8。我們將用函數替換所有內容。 扔掉設計模式。 刪除面向對象。 對! 我們走吧!
等一下
Java 8已經問世了一年多,而這種興奮又回到了日常業務中。
baeldung.com從2015年5月開始執行的一項非代表性研究發現, 他們的讀者中有38%已采用Java 8 。 在此之前,Typsafe在2014年末進行的一項研究聲稱,其用戶中Java 8的采用率為27% 。
這對您的代碼庫意味著什么?
某些Java 7-> Java 8遷移重構是理所當然的。 例如,將Callable傳遞給ExecutorService :
ExecutorService s = ...// Java 7 - meh... Future<String> f = s.submit(new Callable<String>() {@Overridepublic String call() {return "Hello World";}} );// Java 8 - of course! Future<String> f = s.submit(() -> "Hello World");匿名類樣式實際上并沒有在此添加任何值。
除了這些容易理解的話題之外,還有其他不太明顯的話題。 例如,是否使用外部迭代器還是內部迭代器 。 另請參閱Neil Gafter于2007年發表的有關永恒主題的有趣讀物: http ://gafter.blogspot.ch/2007/07/internal-versus-external-iterators.html
以下兩個邏輯的結果相同
List<Integer> list = Arrays.asList(1, 2, 3);// Old school for (Integer i : list)System.out.println(i);// "Modern" list.forEach(System.out::println);我主張“現代”方法應格外小心,即僅在您真正受益于內部功能迭代時(例如,通過Stream的map() , flatMap()和其他操作鏈接一組操作map() ,才應格外小心。
與經典方法相比,這是“現代”方法的缺點的簡短列表:
1.績效–您將因此而蒙受損失
Angelika Langer在她的文章以及她在會議上發表的相關演講中已經很好地總結了這個話題:
https://jaxenter.com/java-performance-tutorial-how-fast-are-the-java-8-streams-118830.html
在許多情況下,性能并不重要,因此您不應該進行任何過早的優化-因此您可能會聲稱此參數本身并不是真正的參數。 但是在這種情況下,我會反駁這種態度,說Stream.forEach()與普通的for循環相比的開銷如此之大,以至于默認情況下使用它只會在您的所有計算機上堆積很多無用的CPU周期應用。 如果僅根據循環樣式的選擇來談論將CPU消耗提高10%-20%,那么我們所做的根本就是錯誤的。 是的–各個循環無關緊要,但是可以避免整個系統的負擔。
這是Angelika在普通循環上的基準測試結果,在裝箱的整數列表中找到最大值:
ArrayList, for-loop : 6.55 ms ArrayList, seq. stream: 8.33 ms在其他情況下,當我們對原始數據類型執行相對簡單的計算時,我們絕對應該退回到經典的for循環(最好是數組而不是集合)。
這是Angelika在普通循環上的基準測試結果,在原始整數數組中找到最大值:
int-array, for-loop : 0.36 ms int-array, seq. stream: 5.35 ms過早的優化效果不好,但是避免過早優化的貨運教育更加糟糕。 重要的是要反思我們所處的環境,并在這種環境下做出正確的決定。 我們之前已經寫過關于性能的博客,請參閱我們的文章《 Java十大簡單性能優化》。
2.可讀性–至少對于大多數人而言
我們是軟件工程師。 我們將始終討論我們的代碼樣式,就像它真的很重要一樣。 例如,空格或花括號。
我們這樣做的原因是因為軟件維護很困難。 特別是別人編寫的代碼。 很久以前。 在切換到Java之前誰可能只寫了C代碼。
當然,在到目前為止的示例中,我們確實沒有可讀性問題,這兩個版本可能是等效的:
List<Integer> list = Arrays.asList(1, 2, 3);// Old school for (Integer i : list)System.out.println(i);// "Modern" list.forEach(System.out::println);但是這里發生了什么:
List<Integer> list = Arrays.asList(1, 2, 3);// Old school for (Integer i : list)for (int j = 0; j < i; j++)System.out.println(i * j);// "Modern" list.forEach(i -> {IntStream.range(0, i).forEach(j -> {System.out.println(i * j);}); });事情開始變得更加有趣和異常。 我不是說“更糟”。 這是實踐和習慣的問題。 并且沒有黑/白問題的答案。 但是,如果其余代碼庫是必須的(可能是這樣),則嵌套范圍聲明和forEach()調用以及lambda肯定是不尋常的,這會在團隊中引起認知沖突 。
您可以構建一些示例,其中命令式方法比等效的功能式方法感覺更尷尬,如此處所示:
勢在必行–功能分離pic.twitter.com/G2cC6iBkDJ
— Mario Fusco(@mariofusco) 2015年3月1日
但是在許多情況下,這是不正確的,并且編寫功能上相對簡單的命令相當的功能相當困難(同樣,效率低下)。 可以在此博客的先前文章中看到一個示例: http : //blog.jooq.org/2015/09/09/how-to-use-java-8-functional-programming-to-generate-an-alphabetic -序列/
在那篇文章中,我們生成了一個字符序列:
A, B, ..., Z, AA, AB, ..., ZZ, AAA…類似于MS Excel中的列:
命令式方法( 最初由Stack Overflow上的一個未命名用戶使用 ):
import static java.lang.Math.*;private static String getString(int n) {char[] buf = new char[(int) floor(log(25 * (n + 1)) / log(26))];for (int i = buf.length - 1; i >= 0; i--) {n--;buf[i] = (char) ('A' + n % 26);n /= 26;}return new String(buf); }……在簡潔的層次上可能勝過功能性的:
import java.util.List;import org.jooq.lambda.Seq;public class Test {public static void main(String[] args) {int max = 3;List<String> alphabet = Seq.rangeClosed('A', 'Z').map(Object::toString).toList();Seq.rangeClosed(1, max).flatMap(length ->Seq.rangeClosed(1, length - 1).foldLeft(Seq.seq(alphabet), (s, i) -> s.crossJoin(Seq.seq(alphabet)).map(t -> t.v1 + t.v2))).forEach(System.out::println);} }并且它已經在使用jOOλ來簡化編寫功能性Java的工作。
3.可維護性
讓我們再次考慮前面的示例。 現在,我們不對值進行相乘,而是對它們進行除法。
List<Integer> list = Arrays.asList(1, 2, 3);// Old school for (Integer i : list)for (int j = 0; j < i; j++)System.out.println(i / j);// "Modern" list.forEach(i -> {IntStream.range(0, i).forEach(j -> {System.out.println(i / j);}); });顯然,這是自找麻煩,我們可以在異常堆棧跟蹤中立即看到問題。
老套
Exception in thread "main" java.lang.ArithmeticException: / by zeroat Test.main(Test.java:13)現代
Exception in thread "main" java.lang.ArithmeticException: / by zeroat Test.lambda$1(Test.java:18)at java.util.stream.Streams$RangeIntSpliterator.forEachRemaining(Streams.java:110)at java.util.stream.IntPipeline$Head.forEach(IntPipeline.java:557)at Test.lambda$0(Test.java:17)at java.util.Arrays$ArrayList.forEach(Arrays.java:3880)at Test.main(Test.java:16)哇。 我們只是…嗎? 是。 這就是為什么我們首先在項目1中遇到性能問題的原因。 對于JVM和庫而言,內部迭代只是要做更多的工作。 這是一個非常簡單的用例,我們可以用AA, AB, .., ZZ系列的產品展示同樣的東西。
從維護的角度來看,函數式編程風格比命令式編程要困難得多,尤其是當您在傳統代碼中盲目地將兩種風格混合在一起時。
結論
這通常是一個關于函數式編程,關于聲明式編程的博客。 我們喜歡lambda。 我們喜歡SQL。 結合起來,它們可以創造奇跡 。
但是,當您遷移到Java 8并考慮在代碼中使用更多功能的樣式時,請注意FP并不總是總會更好–出于各種原因。 實際上,它從來沒有“更好”,它只是不同而已,它使我們對問題有不同的推理。
我們的Java開發人員將需要進行練習,并且對何時使用FP以及何時堅持使用OO /命令式有一個直觀的了解。 通過適當的實踐,將兩者結合起來將有助于我們改進軟件。
或者,用鮑伯叔叔的話來說:
底線就是這個。 當您知道OO編程是什么時,它就是好的。 當您知道函數式編程是什么時,它就是好的。 一旦了解了功能性OO編程,它也是不錯的選擇。
http://blog.cleancoder.com/uncle-bob/2014/11/24/FPvsOO.html
翻譯自: https://www.javacodegeeks.com/2015/12/3-reasons-shouldnt-replace-loops-stream-foreach.html
總結
以上是生活随笔為你收集整理的为什么不应该用Stream forEach替换for循环的3个原因的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java jsr_分叉并加入Java 7
- 下一篇: 拨号上网如何接无线路由器拨号上网如何链连