惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
量子位
M
MIT News - Artificial intelligence
Y
Y Combinator Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Google DeepMind News
Google DeepMind News
Hugging Face - Blog
Hugging Face - Blog
博客园_首页
雷峰网
雷峰网
I
InfoQ
罗磊的独立博客
博客园 - 聂微东
酷 壳 – CoolShell
酷 壳 – CoolShell
大猫的无限游戏
大猫的无限游戏
D
Docker
H
Hackread – Cybersecurity News, Data Breaches, AI and More
腾讯CDC
博客园 - 三生石上(FineUI控件)
The GitHub Blog
The GitHub Blog
K
Kaspersky official blog
P
Privacy & Cybersecurity Law Blog
S
SegmentFault 最新的问题
T
Threat Research - Cisco Blogs
H
Help Net Security
小众软件
小众软件
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
C
CERT Recently Published Vulnerability Notes
WordPress大学
WordPress大学
T
Tenable Blog
T
The Blog of Author Tim Ferriss
C
Cisco Blogs
Simon Willison's Weblog
Simon Willison's Weblog
博客园 - Franky
A
Arctic Wolf
T
Threatpost
Scott Helme
Scott Helme
C
Cybersecurity and Infrastructure Security Agency CISA
D
Darknet – Hacking Tools, Hacker News & Cyber Security
T
The Exploit Database - CXSecurity.com
G
GRAHAM CLULEY
Security Latest
Security Latest
Spread Privacy
Spread Privacy
L
LINUX DO - 热门话题
V
Vulnerabilities – Threatpost
P
Privacy International News Feed
S
Schneier on Security
Latest news
Latest news
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
C
Cyber Attacks, Cyber Crime and Cyber Security
C
CXSECURITY Database RSS Feed - CXSecurity.com

CAYZLH

MacBook上备忘录APP的快捷键和手势 让Google屏蔽某些搜索结果 WSL自定义安装Ubuntu typora自动上传图片配置 自建giscus服务 Windows下结束指定端口的进程 批量修改maven多模块版本号 使用winsw部署SpringBoot项目 Gson简易指南 docker搭建Consul集群 使用Docker部署SpringBoot项目 Docker使用redis镜像 Docker使用rabbitmq Docker使用zookeeper Docker使用MySQL Dockerfile常用指令 Docker免sudo操作 雷鸟电视去广告 使用ADB卸载MIUI系统应用 利用GitHub做图床 将网站变成灰色 Github上传大文件 Android远程调试命令adb vscode快捷键的使用 SpringBoot文件上传异常处理 SpringBoot封装JedisUtils工具类 SpringBoot自动部署脚本 使用Spring读取文件的几种方式 Spring统一异常返回 利用Github做Maven私服 Redis常用指令 Maven常用指令 使用mysqldump导出数据 SpringBoot动态切换多数据源 Java8的日期处理实践 lambda表达式语法 使用Optional优雅地判空 Android Support vs AndroidX 「Vue」Runtime-Complier和Runtime-only的区别 RecyclerView使用记录 动态设置布局之LayoutInflater Redis分布式锁的几种方案 打造一个舒服的写作环境(Hexo) 深入了解与使用ThreadLocal Maven中dependencyManagement的作用 Redis配置认证密码 nginx简单配置示例 Linux(macOS)换源 Linux搭建Git服务器 Linux配置ssh使用公钥登录远程服务器 Linux下find与exec的使用 Linux排查Java问题工具单 Linux常用命令 MySql数据库优化细节 API签名验证方案 Java集合框架 Redis基础知识总结 Spring事务管理 Redis为什么这么快 SpringBoot实现Jwt单点登录 SpringBoot实现Redis分布式锁 Spring面向切面编程(知识梳理) SpringBoot异步请求和异步调用 Mybatis中用到的几种设计模式 Docker Swarm RocketMQ相关流程图/原理图 SonarQube的安装与使用 宝塔面板部署vue项目 Tomcat的三种接收请求处理方式 Docker知识扫盲 安装Jenkins并用于部署SpringBoot项目 Tomcat单机多实例部署 Maven中Scope的分类 Springboot集成Shiro(前后端分离) Linux搭建frp服务(内网穿透) Redis高逼格指令 Docker私有仓库的搭建与使用 Dockerfile使用介绍 Redis的使用场景 Docker的网络模式
Stream表达式语法
Ant丶 · 2022-03-11 · via CAYZLH

Stream初体验

A sequence of elements supporting sequential and parallel aggregate operations.

以上是Java里面是对Stream的定义,解读一下这句话:

  1. Stream是元素的集合,这点让Stream看起来用些类似Iterator;
  2. 可以支持顺序和并行的对原Stream进行汇聚的操作;

可以把Stream当成一个高级版本的Iterator。

原始版本的Iterator,用户只能一个一个的遍历元素并对其执行某些操作;

高级版本的Stream,用户只要给出需要对其包含的元素执行什么操作,比如“过滤掉长度大于10的字符串”、“获取每个字符串的首字母”等,具体这些操作如何应用到每个元素上,就给Stream就好了!

下面一段代码直观的认识一下Stream:


List<Integer> nums = Lists.newArrayList(1,null,3,4,null,6);
nums.stream().filter(num -> num != null).count();

上面这段代码是获取一个List中,元素不为null的个数。我们现在开始深入解刨这个例子,完成以后你可能可以基本掌握Stream的用法!

剖析Stream通用语法

剖析Stream通用语法

图片就是对于Stream例子的一个解析,可以很清楚的看见:原本一条语句被三种颜色的框分割成了三个部分。

红色框中的语句是一个Stream的生命开始的地方,负责创建一个Stream实例;

绿色框中的语句是赋予Stream灵魂的地方,把一个Stream转换成另外一个Stream,红框的语句生成的是一个包含所有nums变量的Stream,进过绿框的filter方法以后,重新生成了一个过滤掉原nums列表所有null以后的Stream;

蓝色框中的语句是丰收的地方,把Stream的里面包含的内容按照某种算法来汇聚成一个值,例子中是获取Stream中包含的元素个数。

剖析Stream通用语法

在此我们总结一下使用Stream的基本步骤:

  1. 创建Stream;
  2. 转换Stream,每次转换原有Stream对象不改变,返回一个新的Stream对象(可以有多次转换);
  3. 对Stream进行聚合(Reduce)操作,获取想要的结果;

创建Stream

最常用的创建Stream有两种途径:

  1. 通过Stream接口的静态工厂方法(注意:Java8里接口可以带静态方法);
  2. 通过Collection接口的默认方法(默认方法:Default method,也是Java8中的一个新特性,就是接口中的一个带有实现的方法)–stream(),把一个Collection对象转换成Stream

使用Stream静态方法来创建Stream

  1. of方法:有两个overload方法,一个接受变长参数,一个接口单一值

    Stream<Integer> integerStream = Stream.of(1, 2, 3, 5);
    Stream<String> stringStream = Stream.of("taobao");
  2. generator方法:生成一个无限长度的Stream,其元素的生成是通过给定的Supplier(这个接口可以看成一个对象的工厂,每次调用返回一个给定类型的对象)

    Stream.generate(new Supplier<Double>() {
    @Override
    public Double get() {
    return Math.random();
    }
    });
    Stream.generate(() -> Math.random());
    Stream.generate(Math::random);

    三条语句的作用都是一样的,只是使用了lambda表达式和方法引用的语法来简化代码。每条语句其实都是生成一个无限长度的Stream,其中值是随机的。这个无限长度Stream是懒加载,一般这种无限长度的Stream都会配合Stream的limit()方法来用。

  3. iterate方法:也是生成无限长度的Stream,和generator不同的是,其元素的生成是重复对给定的种子值(seed)调用用户指定函数来生成的。其中包含的元素可以认为是:seed,f(seed),f(f(seed))无限循环

    Stream.iterate(1, item -> item + 1).limit(10).forEach(System.out::println);

    这段代码就是先获取一个无限长度的正整数集合的Stream,然后取出前10个打印。千万记住使用limit方法,不然会无限打印下去。

通过Collection子类获取Stream

查看Java doc就可以发现Collection接口有一个stream方法,所以其所有子类都都可以获取对应的Stream对象。

public interface Collection<E> extends Iterable<E> {

default Stream<E> stream() {
return StreamSupport.stream(spliterator(), false);
}
}

转换Stream

转换Stream其实就是把一个Stream通过某些行为转换成一个新的Stream。Stream接口中定义了几个常用的转换方法,下面挑选几个常用的转换方法来解释。

  1. distinct: 对于Stream中包含的元素进行去重操作(去重逻辑依赖元素的equals方法),新生成的Stream中没有重复的元素;

    distinct方法示意图:

    distinct方法示意图

  2. filter: 对于Stream中包含的元素使用给定的过滤函数进行过滤操作,新生成的Stream只包含符合条件的元素;

    filter方法示意图:

    filter方法示意图

  3. map: 对于Stream中包含的元素使用给定的转换函数进行转换操作,新生成的Stream只包含转换生成的元素

    这个方法有三个对于原始类型的变种方法,分别是:mapToIntmapToLongmapToDouble。这三个方法也比较好理解,比如mapToInt就是把原始Stream转换成一个新的Stream,这个新生成的Stream中的元素都是int类型。之所以会有这样三个变种方法,可以免除自动装箱/拆箱的额外消耗;

    map方法示意图:

    map方法示意图

  4. flatMap:和map类似,不同的是其每个元素转换得到的是Stream对象,会把子Stream中的元素压缩到父集合中;

    flatMap方法示意图:

    `flatMap`方法示意图

  5. peek: 生成一个包含原Stream的所有元素的新Stream,同时会提供一个消费函数(Consumer实例),新Stream每个元素被消费的时候都会执行给定的消费函数;

    peek方法示意图:

    peek方法示意图

  6. limit: 对一个Stream进行截断操作,获取其前N个元素,如果原Stream中包含的元素个数小于N,那就获取其所有的元素;

    limit方法示意图:

    limit方法示意图

  7. skip: 返回一个丢弃原Stream的前N个元素后剩下元素组成的新Stream,如果原Stream中包含的元素个数小于N,那么返回空Stream;

    skip方法示意图:

    skip方法示意图

  8. 综合使用:

    List<Integer> nums = Lists.newArrayList(1,1,null,2,3,4,null,5,6,7,8,9,10);
    System.out.println(“sum is:”+nums.stream().filter(num -> num != null).
    distinct().mapToInt(num -> num * 2).
    peek(System.out::println).skip(2).limit(4).sum());

    这段代码演示了上面介绍的所有转换方法(除了flatMap)。

    简单解释一下这段代码的含义:给定一个Integer类型的List,获取其对应的Stream对象,然后进行过滤掉null,再去重,再每个元素乘以2,再每个元素被消费的时候打印自身,在跳过前两个元素,最后去前四个元素进行加和运算(解释一大堆,很像废话,因为基本看了方法名就知道要做什么了。这个就是声明式编程的一大好处!)。

  9. 性能问题

    可能会有这样的疑问:在对于一个Stream进行多次转换操作,每次都对Stream的每个元素进行转换,而且是执行多次,这样时间复杂度就是一个for循环里把所有操作都做掉的N(转换的次数)倍啊。其实不是这样的,转换操作都是lazy的,多个转换操作只会在汇聚操作的时候融合起来,一次循环完成。可以这样简单的理解,Stream里有个操作函数的集合,每次转换操作就是把转换函数放入这个集合中,在汇聚操作的时候循环Stream对应的集合,然后对每个元素执行所有的函数。

汇聚(Reduce)Stream

Java doc中对于其定义:

A reduction operation (also called a fold) takes a sequence of input elements and combines them into a single summary result by repeated application of a combining operation, such as finding the sum or maximum of a set of numbers, or accumulating elements into a list. The streams classes have multiple forms of general reduction operations, called reduce() and collect(), as well as multiple specialized reduction forms such as sum(), max(), or count().

汇聚操作(也称为折叠)接受一个元素序列为输入,反复使用某个合并操作,把序列中的元素合并成一个汇总的结果。比如查找一个数字列表的总和或者最大值,或者把这些数字累积成一个List对象。Stream接口有一些通用的汇聚操作,比如reduce()和collect();也有一些特定用途的汇聚操作,比如sum(),max()和count()。注意:sum方法不是所有的Stream对象都有的,只有IntStream、LongStream和DoubleStream是实例才有。

分两部分来介绍汇聚操作:

  1. 可变汇聚:把输入的元素们累积到一个可变的容器中,比如Collection或者StringBuilder;
  2. 其他汇聚:除去可变汇聚剩下的,一般都不是通过反复修改某个可变对象,而是通过把前一次的汇聚结果当成下一次的入参,反复如此。比如reduce,count,allMatch;

可变汇聚

可变汇聚对应的只有一个方法:collect,正如其名字显示的,它可以把Stream中的要有元素收集到一个结果容器中(比如Collection)。先看一下最通用的collect方法的定义(还有其他override方法):

<R> R collect(Supplier<R> supplier,
BiConsumer<R, ? super T> accumulator,
BiConsumer<R, R> combiner);

先来看看这三个参数的含义:

Supplier supplier是一个工厂函数,用来生成一个新的容器;

BiConsumer accumulator也是一个函数,用来把Stream中的元素添加到结果容器中;

BiConsumer combiner还是一个函数,用来把中间状态的多个结果容器合并成为一个(并发的时候会用到);

来段代码:

List<Integer> nums = Lists.newArrayList(1,1,null,2,3,4,null,5,6,7,8,9,10);
List<Integer> numsWithoutNull = nums.stream().filter(num -> num != null).
collect(() -> new ArrayList<Integer>(),
(list, item) -> list.add(item),
(list1, list2) -> list1.addAll(list2));

上面这段代码就是对一个元素是Integer类型的List,先过滤掉全部的null,然后把剩下的元素收集到一个新的List中。进一步看一下collect方法的三个参数,都是lambda形式的函数。

  • 第一个函数生成一个新的ArrayList实例;
  • 第二个函数接受两个参数,第一个是前面生成的ArrayList对象,二个是stream中包含的元素,函数体就是把stream中的元素加入ArrayList对象中。第二个函数被反复调用直到原stream的元素被消费完毕;
  • 第三个函数也是接受两个参数,这两个都是ArrayList类型的,函数体就是把第二个ArrayList全部加入到第一个中;

但是上面的collect方法调用也有点太复杂了,来看一下collect方法另外一个override的版本:

<R, A> R collect(Collector<? super T, A, R> collector);

Java8还给我们提供了Collector的工具类,其中已经定义了一些静态工厂方法,比如:Collectors.toCollection()收集到Collection中, Collectors.toList()收集到List中和Collectors.toSet()收集到Set中。

使用Collectors对于代码的简化:

List<Integer> numsWithoutNull = nums.stream().filter(num -> num != null).
collect(Collectors.toList());

其他汇聚

  • reduce方法:reduce方法非常的通用,后面介绍的count,sum等都可以使用其实现。

reduce方法的第一种形式,其方法定义如下:

Optional<T> reduce(BinaryOperator<T> accumulator);

接受一个BinaryOperator类型的参数,在使用的时候可以用lambda表达式来。

List<Integer> ints = Lists.newArrayList(1,2,3,4,5,6,7,8,9,10);
System.out.println("ints sum is:" + ints.stream().reduce((sum, item) -&gt; sum + item).get());

可以看到reduce方法接受一个函数,这个函数有两个参数,第一个参数是上次函数执行的返回值(也称为中间结果),第二个参数是stream中的元素,这个函数把这两个值相加,得到的和会被赋值给下次执行这个函数的第一个参数。要注意的是:第一次执行的时候第一个参数的值是Stream的第一个元素,第二个参数是Stream的第二个元素。这个方法返回值类型是Optional,这是Java8防止出现NPE的一种可行方法,后面的文章会详细介绍,这里就简单的认为是一个容器,其中可能会包含0个或者1个对象。

reduce方法还有一个很常用的变种:

T reduce(T identity, BinaryOperator<T> accumulator);

这个定义上上面已经介绍过的基本一致,不同的是:它允许用户提供一个循环计算的初始值,如果Stream为空,就直接返回该值。而且这个方法不会返回Optional,因为其不会出现null值。

List<Integer> ints = Lists.newArrayList(1,2,3,4,5,6,7,8,9,10);
System.out.println("ints sum is:" + ints.stream().reduce(0, (sum, item) -> sum + item));
  • count方法:获取Stream中元素的个数
List<Integer> ints = Lists.newArrayList(1,2,3,4,5,6,7,8,9,10);
System.out.println("ints sum is:" + ints.stream().count());

其他方法:

  • allMatch:是不是Stream中的所有元素都满足给定的匹配条件
  • anyMatch:Stream中是否存在任何一个元素满足匹配条件
  • findFirst: 返回Stream中的第一个元素,如果Stream为空,返回空Optional
  • noneMatch:是不是Stream中的所有元素都不满足给定的匹配条件
  • max和min:使用给定的比较器(Operator),返回Stream中的最大|最小值