如果你使用了这个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 使用的角度来看,这不太正常。如果我调用一个方法,我肯定希望它能够执行,即使它可能抛出一个异常,但是在这里却什么也没发生。
❝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在一场技术讨论中提及的。
那么JDK给的建议是什么
尽量不使用count()
,甚至Stream.collect(Collectors.counting())
也少用,如果你想改变元素,根据情况使用map
操作或者foreach
操作。如果你在20天后Java17发布后进行升级一定要注意这一点。不过说实话peek()
用着挺爽的,这么改的话有点可惜了,不知道你对此有什么看法,欢迎留言讨论。
2021-08-24
2021-08-21