如果你使用了這個Stream流操作,升級Java17有可能會出問題
在Java 8 中,甚至到Java 16 中執行下面的Stream流操作
- Stream.of(1, 2, 3, 4)
- .skip(1)
- .limit(2)
- .peek(System.out::println)
- .count();
都會跳過元素1,打印元素2以及3,最終計數為2,我想大家對此應該都沒有異議。
但是從Java 17 開始,再次執行上面的代碼,跳過元素1,計數為2。等等…… 是不是少執行了點什么?
是的,不打印元素`2`和`3`了?
從 API 使用的角度來看,這不太正常。如果我調用一個方法,我肯定希望它能夠執行,即使它可能拋出一個異常,但是在這里卻什么也沒發生。
- 是的,不打印元素`2`和`3`了?
This method exists mainly to support debugging, where you want to see the elements as they flow past a certain point in a pipeline。
這是對Stream的peek(Consumer)方法的一個說明,大意是:雖然我們可以在流中通過peek執行一些利用中間操作消費元素的方法,胖哥為此還寫過相關的文章。不過這個API的本意設計并不是為了改變Stream流中元素的中間態,而是為了Debug,為了讓你能夠觀察到管道中的元素途經的點:
- Stream.of("one", "two", "three", "four")
- .filter(e -> e.length() > 3)
- // 觀察正在被長度大于3規則過濾的元素
- .peek(e -> System.out.println("Filtered value: " + e))
- .map(String::toUpperCase)
- // 觀察正在被轉大寫的元素
- .peek(e -> System.out.println("Mapped value: " + e))
- .collect(Collectors.toList());
也就是說使用peek()改變Stream元素是在Debug中“副作用”的一個操作。
Stream流的大小在執行跳過操作skip(n)和限制長度操作limit(n)后,流的大小長度是已經預知的,為了獲得流的大小沒必要去遍歷流的元素,跳過了遍歷就不能再通過peek()觀察元素了。
允許流不執行對結果沒有任何作用的操作,例如排序一個已經排序的流。這個操作的結果是已知的,不需要迭代元素,也不會影響結果,所以不迭代。所以不具有觀察(peek)的價值。
我敢說會有大量的項目、甚至是優秀的開源項目會受到這個新機制的影響,胖哥也在項目中使用了圖片。所以如果你看到這篇文章而且使用peek()做了一些“副作用”操作,就需要評估升級Java17帶來的影響了。
消息來源
這一新機制是Java Champion、Jetbrains核心開發者塔吉爾·瓦列夫(Tagir Valeev)和Oracle Java 語言架構師Brian Goetz在一場技術討論中提及的。
Brian Goetz
那么JDK給的建議是什么
盡量不使用count(),甚至Stream.collect(Collectors.counting())也少用,如果你想改變元素,根據情況使用map操作或者foreach操作。如果你在20天后Java17發布后進行升級一定要注意這一點。不過說實話peek()用著挺爽的,這么改的話有點可惜了,不知道你對此有什么看法,歡迎留言討論。