Java 16 中新增的 Stream 接口的一些思考
这里先提一个题外话,如果想看 JDK 不同版本之间有何差异,增加或者删除了哪些 API,可以通过下面这个链接查看:
路径中的两个版本就是要对比的两个版本,其界面如下:
同时,我们也可以通过 JDK 内置 jdeps 工具查找过期以及废弃API以及对应的替换
jdeps --jdk-internals -R --class-path 'libs/*' $project
libs是你的所有依赖的目录,$project是你的项目jar包,示例输出:
... JDK Internal API Suggested Replacement ---------------- --------------------- sun.misc.BASE64Encoder Use java.util.Base64 @since 1.8 sun.reflect.Reflection Use java.lang.StackWalker @since 9
Java 16 中针对 Stream API 有两个更新:
增加了
mapMulti和
toList这两个 API。
mapMulti
mapMulti其实主要是对现有的
flatMap在某些场景使用起来不够合适的补充。
flatMap是将一个对象映射为多个对象之后继续 Stream,例如将
List<List<Integer>>里面的每一个数字取出,转换成一个新的
List<Integer>:
integerLists.stream().flatMap(integers -> integers.stream()).collect(Collectors.toList());
这对于每个元素本身就是集合类型的场景来说,非常适用。我们再来看一个例子,假设有邮件这个 Record 类,包含 id,以及发送到的邮箱和抄送到的邮箱:
record Mail(int id, Set<String> sendTo, Set<String> cc) {}
我们想找到一批邮件的所有不同的联系人,最后放到一个 List 中,可能会这么写:
Set<String> collect = mails.stream().flatMap(mail -> { Set<String> result = new HashSet<>(); result.addAll(mail.sendTo()); result.addAll(mail.cc()); return result.stream(); }).collect(Collectors.toSet());
但是,这样写显然很不优雅,首先是对于每一个 Mail 都创建了额外的 Set 和对应的 Stream,并且,对于每个 mail 的 sendTo 还有 cc 都遍历了两遍(addAll 一遍,后续 Stream 又一遍)。其实我们的目前只是将 mail 中的 cc 以及 sendTo 取出来,用于参与后续的 Stream。在这种场景下,就非常适合用 mapMulti:
Set<String> collect = mails.stream().<String>mapMulti((mail, consumer) -> { mail.cc().forEach(consumer::accept); mail.sendTo().forEach(consumer::accept); }).collect(Collectors.toSet());
可以看出:
- mapMulti 的入参是一个
BiConsumer
,其实就是使用其参数中的 consumer 接收参与 Stream 后续的对象 - mapMulti 的思路就是将参数中的需要参与后续 Stream 的对象传入 consumer 来继续 Stream
- consumer 没有限制对象类型,想要限制必须加上形参
<String>
否则最后返回的是Set<Object>
而不是Set<String>
假设 mail 的 sendTo 还有 cc 都需要去其他地方获取,使用 mapMulti 还可以实现:
Set<String> collect = mailIds.stream().<String>mapMulti((mailId, consumer) -> { mailService.getCCById(mailId).forEach(consumer::accept); mailService.getSendToById(mailId).forEach(consumer::accept); }).collect(Collectors.toSet());
还有一些比较有意思的用法,例如混合类型的 List 转换成统一类型:
class C { static void expandIterable(Object e, Consumer<Object> c) { if (e instanceof Iterable<?> elements) { for (Object ie : elements) { expandIterable(ie, c); } } else if (e != null) { c.accept(e); } } public static void main(String[] args) { var nestedList = List.of(1, List.of(2, List.of(3, 4)), 5); Stream<Object> expandedStream = nestedList.stream().mapMulti(C::expandIterable); } }
活用
Optional.ifPresent(Consumer<? super T> action)方法:
Stream.of(Optional.of("0"), Optional.of("1"), Optional.empty()) .mapMulti(Optional::ifPresent) .forEach(System.out::print);
toList
对于 Stream 增加了 toList 直接转换成 List,由于不涉及 collect 里面的截断操作,所以比 collect 占用的内存更小,需要的操作更少并且更快。
之前转换成 List,需要
collect(Collectors.toList()),生成的 List 是
ArrayList,是可变的
但是这次新加的 Api,toList 生成的是 UnmodifiableList
,是不可变的。
所以这两个 API 不能直接互相替换,需要做一些检查确认没有更改才能替换。
微信搜索“我的编程喵”关注公众号,每日一刷,轻松提升技术,斩获各种offer:
- 关于“JAVA中为什么没有了多继承并出现了接口”这一问题引发的一些思考
- WinForms C#:html编辑器工程源码,含直接写WebBrowser的文件流、IPersistStreamInit接口的声明和一些相关的小方法
- java 中一些接口和实现类的区别
- java 接口以及抽象类 一些知识点
- java接口的一些生动的例子
- java内接口,抽象类,继承实现的一些基础知识和实例
- 关于 服务器开发的一些思考-16/01/31
- 聊聊Java中codepoint和UTF-16相关的一些事
- [Java]关于堆和栈的一些思考
- 关于Java内存溢出的一些思考
- Java集合源码学习(16)_BlockingQueue接口的实现ArrayBlockingQueue
- 对几个通用的Java hashCode重写方案的一些思考和探讨
- Java接口的一些认识
- Java 8新增的Stream操作
- Java 反射的一些思考
- 【Java面试题】43 说出一些常用的类,包,接口,请各举5个
- WinForms C#:html编辑器工程源码,含直接写WebBrowser的文件流、IPersistStreamInit接口的声明和一些相关的小方法
- 关于java中Static的一些思考
- JAVA的一些Stream
- 关于近期学习java se篇的小结及一些学习路线的思考