这里先提一个题外话,如果想看 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 16 中新增的 Stream 接口的一些思考的更多相关文章

  1. JEP解读与尝鲜系列4 - Java 16 中对于 Project Valhalla 的铺垫

    这是 JEP 解读与尝鲜系列的第 4 篇,之前的文章如下: JEP解读与尝鲜系列 1 - Java Valhalla与Java Inline class JEP解读与尝鲜系列 2 - JEP 142 ...

  2. java 7中新增的CPU和负载的监控

    java 7中新增的CPU和负载的监控 import java.lang.management.ManagementFactory; import java.lang.management.Opera ...

  3. Java集合中Comparator和Comparable接口的使用

    在Java集合中,如果要比较引用类型泛型的List,我们使用Comparator和Comparable两个接口. Comparable接口 -- 默认比较规则,可比较的 实现该接口表示:这个类的实例可 ...

  4. Java 8 中的抽象类和接口到底有啥区别?

    上一篇栈长发了这篇<Java 8 有多牛逼?打破一切你对接口的认知!>,帮助许多人解开了疑惑,还有读者留言说两者还有啥区别,故引发了此篇: 在我们面试时也会经常遇到面试官问抽象类和接口的区 ...

  5. Java 8 中常用的函数式接口

    函数式接口 函数描述符 Predicate<T> T->boolean Consumer<T> T->void Function<T, R> T-> ...

  6. Java 8 Lambda 表达式及 Stream 在集合中的用法

    简介 虽然 Java 8 已经发布有一段时间了,但是关于 Java 8 中的 Lambda 表达式最近才开始系统的学习,刚开始就被 Stream 的各种骚操作深深的吸引住了,简直漂亮的不像 Java. ...

  7. Java 中的锁——Lock接口

    Java SE5之后,并发包中新增了Lock接口(以及相关实现类)用来实现锁功能.虽然它少了(通过synchronized块或者方法所提供的)隐式获取释放锁的便捷性,但是却拥有了锁获取与释放的操作性. ...

  8. 官方正式发布 Java 16

    前言 就在2021/03/16,官方正式发布了Java 16.我们可以下载使用Java 16了. 特性 向量API(孵化) 在运行期,Vector 表示向量计算可以可靠地编译成支持CPU架构上的最佳矢 ...

  9. Java SE 19 新增特性

    Java SE 19 新增特性 作者:Grey 原文地址: 博客园:Java SE 19 新增特性 CSDN:Java SE 19 新增特性 源码 源仓库: Github:java_new_featu ...

随机推荐

  1. ViewStub应用

    在开发应用程序的时候,会遇到这样的情况,在运行时动态的根据条件来决定显示哪个View或哪个布局,可以把可能用到的View都写在上面,先把他们的可见性设置为View.GONE,然后在代码中动态的更改它的 ...

  2. Mysql 常见报错和疑问汇总

    1.初始化数据库的时候报错 error while loading shared libraries: libstdc++.so.5: cannot open shared object file:  ...

  3. delete() and free() in C++

    In C++, delete operator should only be used either for the pointers pointing to the memory allocated ...

  4. 特定场景下的PLC 远程控制和数据读取

    最近有位博友提出了一种应用场景,根据工作中实际遇到的类似的产品应用场景,记录下自己的解决方案. 场景: 需要在云端控制和采集各个站点的PLC数据.各个站点是分散的,每个站点有公网访问能力,但是分散站点 ...

  5. JS21. 使用原生JS封装一个公共的Alert插件(HTML5: Shadow Dom)

    效果预览 Shadow DOM Web components  的一个重要属性是封装--可以将标记结构.样式和行为隐藏起来,并与页面上的其他代码相隔离,保证不同的部分不会混在一起,可使代码更加干净.整 ...

  6. Snort 入侵检测系统

    Snort 入侵检测系统 一.实验目的 1.掌握snort IDS工作原理 2.应用snort 三种方式工作 二.实验环境 系统环境:Windows环境, kali环境 三.实验原理 1.snort ...

  7. 2、Redis的安装

    一.Windows下Redis安装 下载地址 Redis中文网站 Github地址 1.将下载下来的文件解压到目录 2.双击redis-server.exe运行   出现如下界面证明运行成功 3.双击 ...

  8. 数据恢复binlog2sql

    目录 一.原理及其使用 用途 闪回原理简析 binlog 有三种可选的格式: 来实例演习下来实例演习下 二.准备工作 一.原理及其使用 生产上误删数据.误改数据的现象也是时常发生的现象,作为运维这时候 ...

  9. Oracle命名规则

    1.长度不能超过三十个字符 2. 不要使用Oracle关键字 比如:id  name  table 3. 不能使用数字开头 包含:数字  字母  下划线 美元符号 4.  建议用 英文单词,不要去用中 ...

  10. 转:select、poll、epoll之间的区别总结[整理]

    转:select.poll.epoll之间的区别总结[整理] select,poll,epoll都是IO多路复用的机制.I/O多路复用就通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就 ...