简洁又快速地处理集合——Java8 Stream(下)

上一篇文章我讲解 Stream 流的基本原理,以及它与集合的区别关系,讲了那么多抽象的,本篇文章我们开始实战,讲解流的各个方法以及各种操作

没有看过上篇文章的可以先点击进去学习一下 简洁又快速地处理集合——Java8 Stream(上),当然你直接看这篇也可以,不过了解其本身才能更融会贯通哦。

值得注意的是:学习 Stream 之前必须先学习 lambda 的相关知识。本文也假设读者已经掌握 lambda 的相关知识。

本篇文章主要内容:

  • 流基本的常用方法
  • 一种特化形式的流——数值流
  • Optional 类
  • 如何构建一个流
  • collect 方法
  • 并行流相关问题

一. 一般方法

首先我们先创建一个 Person 泛型的 List

List<Person> list = new ArrayList<>();
list.add(new Person("jack", 20));
list.add(new Person("mike", 25));
list.add(new Person("tom", 30));

Person 类包含年龄和姓名两个成员变量

private String name;
private int age;

1. stream() / parallelStream()

最常用到的方法,将集合转换为流

List list = new ArrayList();
// return Stream<E>
list.stream();

而 parallelStream() 是并行流方法,能够让数据集执行并行操作,后面会更详细地讲解

2. filter(T -> boolean)

保留 boolean 为 true 的元素

保留年龄为 20 的 person 元素
list = list.stream().filter(person -> person.getAge() == 20).collect(toList());打印输出 [Person{name='jack', age=20}]

collect(toList()) 可以把流转换为 List 类型,这个以后会讲解

3. distinct()

去除重复元素,这个方法是通过类的 equals 方法来判断两个元素是否相等的

如例子中的 Person 类,需要先定义好 equals 方法,不然类似[Person{name='jack', age=20}, Person{name='jack', age=20}] 这样的情况是不会处理的

4. sorted() / sorted((T, T) -> int)

如果流中的元素的类实现了 Comparable 接口,即有自己的排序规则,那么可以直接调用 sorted() 方法对元素进行排序,如 Stream

反之, 需要调用 sorted((T, T) -> int) 实现 Comparator 接口

根据年龄大小来比较:
list = list.stream().sorted((p1, p2) -> p1.getAge() - p2.getAge()).collect(toList());

当然这个可以简化为

list = list.stream().sorted(Comparator.comparingInt(Person::getAge)).collect(toList());

5. limit(long n)

返回前 n 个元素

list = list.stream().limit(2).collect(toList());打印输出 [Person{name='jack', age=20}, Person{name='mike', age=25}]

6. skip(long n)

去除前 n 个元素

list = list.stream().skip(2).collect(toList());打印输出 [Person{name='tom', age=30}]

tips:

  • 用在 limit(n) 前面时,先去除前 m 个元素再返回剩余元素的前 n 个元素
  • limit(n) 用在 skip(m) 前面时,先返回前 n 个元素再在剩余的 n 个元素中去除 m 个元素
list = list.stream().limit(2).skip(1).collect(toList());打印输出 [Person{name='mike', age=25}]

7. map(T -> R)

将流中的每一个元素 T 映射为 R(类似类型转换)

List<String> newlist = list.stream().map(Person::getName).collect(toList());

newlist 里面的元素为 list 中每一个 Person 对象的 name 变量

8. flatMap(T -> Stream)

将流中的每一个元素 T 映射为一个流,再把每一个流连接成为一个流

List<String> list = new ArrayList<>();
list.add("aaa bbb ccc");
list.add("ddd eee fff");
list.add("ggg hhh iii");list = list.stream().map(s -> s.split(" ")).flatMap(Arrays::stream).collect(toList());

上面例子中,我们的目的是把 List 中每个字符串元素以" "分割开,变成一个新的 List。
首先 map 方法分割每个字符串元素,但此时流的类型为 Stream<String[ ]>,因为 split 方法返回的是 String[ ] 类型;所以我们需要使用 flatMap 方法,先使用Arrays::stream将每个 String[ ] 元素变成一个 Stream 流,然后 flatMap 会将每一个流连接成为一个流,最终返回我们需要的 Stream

9. anyMatch(T -> boolean)

流中是否有一个元素匹配给定的 T -> boolean 条件

是否存在一个 person 对象的 age 等于 20:
boolean b = list.stream().anyMatch(person -> person.getAge() == 20);

10. allMatch(T -> boolean)

流中是否所有元素都匹配给定的 T -> boolean 条件

11. noneMatch(T -> boolean)

流中是否没有元素匹配给定的 T -> boolean 条件

12. findAny() 和 findFirst()

  • findAny():找到其中一个元素 (使用 stream() 时找到的是第一个元素;使用 parallelStream() 并行时找到的是其中一个元素)
  • findFirst():找到第一个元素

值得注意的是,这两个方法返回的是一个 Optional 对象,它是一个容器类,能代表一个值存在或不存在,这个后面会讲到

13. reduce((T, T) -> T) 和 reduce(T, (T, T) -> T)

用于组合流中的元素,如求和,求积,求最大值等

计算年龄总和:
int sum = list.stream().map(Person::getAge).reduce(0, (a, b) -> a + b);
与之相同:
int sum = list.stream().map(Person::getAge).reduce(0, Integer::sum);

其中,reduce 第一个参数 0 代表起始值为 0,lambda (a, b) -> a + b 即将两值相加产生一个新值

同样地:

计算年龄总乘积:
int sum = list.stream().map(Person::getAge).reduce(1, (a, b) -> a * b);

当然也可以

Optional<Integer> sum = list.stream().map(Person::getAge).reduce(Integer::sum);

即不接受任何起始值,但因为没有初始值,需要考虑结果可能不存在的情况,因此返回的是 Optional 类型

13. count()

返回流中元素个数,结果为 long 类型

14. collect()

收集方法,我们很常用的是 collect(toList()),当然还有 collect(toSet()) 等,参数是一个收集器接口,这个后面会另外讲

15. forEach()

返回结果为 void,很明显我们可以通过它来干什么了,比方说:


### 16. unordered()
还有这个比较不起眼的方法,返回一个等效的无序流,当然如果流本身就是无序的话,那可能就会直接返回其本身打印各个元素:
list.stream().forEach(System.out::println);

再比如说 MyBatis 里面访问数据库的 mapper 方法:

向数据库插入新元素:
list.stream().forEach(PersonMapper::insertPerson);

二. 数值流

前面介绍的如
int sum = list.stream().map(Person::getAge).reduce(0, Integer::sum); 计算元素总和的方法其中暗含了装箱成本,map(Person::getAge) 方法过后流变成了 Stream 类型,而每个 Integer 都要拆箱成一个原始类型再进行 sum 方法求和,这样大大影响了效率。

针对这个问题 Java 8 有良心地引入了数值流 IntStream, DoubleStream, LongStream,这种流中的元素都是原始数据类型,分别是 int,double,long

1. 流与数值流的转换

流转换为数值流

  • mapToInt(T -> int) : return IntStream
  • mapToDouble(T -> double) : return DoubleStream
  • mapToLong(T -> long) : return LongStream
IntStream intStream = list.stream().mapToInt(Person::getAge);

当然如果是下面这样便会出错

LongStream longStream = list.stream().mapToInt(Person::getAge);

因为 getAge 方法返回的是 int 类型(返回的如果是 Integer,一样可以转换为 IntStream)

数值流转换为流

很简单,就一个 boxed

Stream<Integer> stream = intStream.boxed();

2. 数值流方法

下面这些方法作用不用多说,看名字就知道:

  • sum()
  • max()
  • min()
  • average() 等...

3. 数值范围

IntStream 与 LongStream 拥有 range 和 rangeClosed 方法用于数值范围处理

  • IntStream : rangeClosed(int, int) / range(int, int)
  • LongStream : rangeClosed(long, long) / range(long, long)

这两个方法的区别在于一个是闭区间,一个是半开半闭区间:

  • rangeClosed(1, 100) :[1, 100]
  • range(1, 100) :[1, 100)

我们可以利用 IntStream.rangeClosed(1, 100) 生成 1 到 100 的数值流

求 1 到 10 的数值总和:
IntStream intStream = IntStream.rangeClosed(1, 10);
int sum = intStream.sum();

三. Optional 类

NullPointerException 可以说是每一个 Java 程序员都非常讨厌看到的一个词,针对这个问题, Java 8 引入了一个新的容器类 Optional,可以代表一个值存在或不存在,这样就不用返回容易出问题的 null。之前文章的代码中就经常出现这个类,也是针对这个问题进行的改进。

Optional 类比较常用的几个方法有:

  • isPresent() :值存在时返回 true,反之 flase
  • get() :返回当前值,若值不存在会抛出异常
  • orElse(T) :值存在时返回该值,否则返回 T 的值

Optional 类还有三个特化版本 OptionalInt,OptionalLong,OptionalDouble,刚刚讲到的数值流中的 max 方法返回的类型便是这个

Optional 类其中其实还有很多学问,讲解它说不定也要开一篇文章,这里先讲那么多,先知道基本怎么用就可以。

四. 构建流

之前我们得到一个流是通过一个原始数据源转换而来,其实我们还可以直接构建得到流。

1. 值创建流

  • Stream.of(T...) : Stream.of("aa", "bb") 生成流
生成一个字符串流
Stream<String> stream = Stream.of("aaa", "bbb", "ccc");
  • Stream.empty() : 生成空流

2. 数组创建流

根据参数的数组类型创建对应的流:

  • Arrays.stream(T[ ])
  • Arrays.stream(int[ ])
  • Arrays.stream(double[ ])
  • Arrays.stream(long[ ])

值得注意的是,还可以规定只取数组的某部分,用到的是Arrays.stream(T[], int, int)

只取索引第 1 到第 2 位的:
int[] a = {1, 2, 3, 4};
Arrays.stream(a, 1, 3).forEach(System.out :: println);打印 2 ,3

3. 文件生成流

Stream<String> stream = Files.lines(Paths.get("data.txt"));

每个元素是给定文件的其中一行

4. 函数生成流

两个方法:

  • iterate : 依次对每个新生成的值应用函数
  • generate :接受一个函数,生成一个新的值
Stream.iterate(0, n -> n + 2)
生成流,首元素为 0,之后依次加 2Stream.generate(Math :: random)
生成流,为 0 到 1 的随机双精度数Stream.generate(() -> 1)
生成流,元素全为 1

五. collect 收集数据

coollect 方法作为终端操作,接受的是一个 Collector 接口参数,能对数据进行一些收集归总操作

1. 收集

最常用的方法,把流中所有元素收集到一个 List, Set 或 Collection 中

  • toList
  • toSet
  • toCollection
List newlist = list.stream.collect(toList());

2. 汇总

(1)counting

用于计算总和:

long l = list.stream().collect(counting());

没错,你应该想到了,下面这样也可以:

long l = list.stream().count();

推荐第二种

(2)summingInt ,summingLong ,summingDouble

summing,没错,也是计算总和,不过这里需要一个函数参数

计算 Person 年龄总和:

int sum = list.stream().collect(summingInt(Person::getAge));

当然,这个可以也简化为:

int sum = list.stream().mapToInt(Person::getAge).sum();

除了上面两种,其实还可以:

int sum = list.stream().map(Person::getAge).reduce(Interger::sum).get();

推荐第二种

由此可见,函数式编程通常提供了多种方式来完成同一种操作

(3)averagingInt,averagingLong,averagingDouble

看名字就知道,求平均数

Double average = list.stream().collect(averagingInt(Person::getAge));

当然也可以这样写

OptionalDouble average = list.stream().mapToInt(Person::getAge).average();

不过要注意的是,这两种返回的值是不同类型的

(4)summarizingInt,summarizingLong,summarizingDouble

这三个方法比较特殊,比如 summarizingInt 会返回 IntSummaryStatistics 类型

IntSummaryStatistics l = list.stream().collect(summarizingInt(Person::getAge));

IntSummaryStatistics 包含了计算出来的平均值,总数,总和,最值,可以通过下面这些方法获得相应的数据

1240

3. 取最值

maxBy,minBy 两个方法,需要一个 Comparator 接口作为参数

Optional<Person> optional = list.stream().collect(maxBy(comparing(Person::getAge)));

我们也可以直接使用 max 方法获得同样的结果

Optional<Person> optional = list.stream().max(comparing(Person::getAge));

4. joining 连接字符串

也是一个比较常用的方法,对流里面的字符串元素进行连接,其底层实现用的是专门用于字符串连接的 StringBuilder

String s = list.stream().map(Person::getName).collect(joining());结果:jackmiketom
String s = list.stream().map(Person::getName).collect(joining(","));结果:jack,mike,tom

joining 还有一个比较特别的重载方法:

String s = list.stream().map(Person::getName).collect(joining(" and ", "Today ", " play games."));结果:Today jack and mike and tom play games.

即 Today 放开头,play games. 放结尾,and 在中间连接各个字符串

5. groupingBy 分组

groupingBy 用于将数据分组,最终返回一个 Map 类型

Map<Integer, List<Person>> map = list.stream().collect(groupingBy(Person::getAge));

例子中我们按照年龄 age 分组,每一个 Person 对象中年龄相同的归为一组

另外可以看出,Person::getAge 决定 Map 的键(Integer 类型),list 类型决定 Map 的值(List 类型)

多级分组

groupingBy 可以接受一个第二参数实现多级分组:

Map<Integer, Map<T, List<Person>>> map = list.stream().collect(groupingBy(Person::getAge, groupBy(...)));

其中返回的 Map 键为 Integer 类型,值为 Map<T, List> 类型,即参数中 groupBy(...) 返回的类型

按组收集数据

Map<Integer, Integer> map = list.stream().collect(groupingBy(Person::getAge, summingInt(Person::getAge)));

该例子中,我们通过年龄进行分组,然后 summingInt(Person::getAge)) 分别计算每一组的年龄总和(Integer),最终返回一个 Map<Integer, Integer>

根据这个方法,我们可以知道,前面我们写的:

groupingBy(Person::getAge)

其实等同于:

groupingBy(Person::getAge, toList())

6. partitioningBy 分区

分区与分组的区别在于,分区是按照 true 和 false 来分的,因此partitioningBy 接受的参数的 lambda 也是 T -> boolean

根据年龄是否小于等于20来分区
Map<Boolean, List<Person>> map = list.stream().collect(partitioningBy(p -> p.getAge() <= 20));打印输出
{false=[Person{name='mike', age=25}, Person{name='tom', age=30}], true=[Person{name='jack', age=20}]
}

同样地 partitioningBy 也可以添加一个收集器作为第二参数,进行类似 groupBy 的多重分区等等操作。

六. 并行

之前我就讲到了 parallelStream 方法能生成并行流,因此你通常可以使用 parallelStream 来代替 stream 方法,但是并行的性能问题非常值得我们思考

比方说下面这个例子

 int i = Stream.iterate(1, a -> a + 1).limit(100).parallel().reduce(0, Integer::sum);

我们通过这样一行代码来计算 1 到 100 的所有数的和,我们使用了 parallel 来实现并行。

但实际上是,这样的计算,效率是非常低的,比不使用并行还低!一方面是因为装箱问题,这个前面也提到过,就不再赘述,还有一方面就是 iterate 方法很难把这些数分成多个独立块来并行执行,因此无形之中降低了效率。

流的可分解性

这就说到流的可分解性问题了,使用并行的时候,我们要注意流背后的数据结构是否易于分解。比如众所周知的 ArrayList 和 LinkedList,明显前者在分解方面占优。

我们来看看一些数据源的可分解性情况

数据源可分解性
ArrayList极佳
LinkedList
IntStream.range极佳
Stream.iterate
HashSet
TreeSet

顺序性

除了可分解性,和刚刚提到的装箱问题,还有一点值得注意的是一些操作本身在并行流上的性能就比顺序流要差,比如:limit,findFirst,因为这两个方法会考虑元素的顺序性,而并行本身就是违背顺序性的,也是因为如此 findAny 一般比 findFirst 的效率要高。


相关阅读

  • 简洁又快速地处理集合——Java8 Stream(上)

猜你喜欢

  • 你必须搞清楚的String,StringBuilder,StringBuffer
  • 分享一些 Java 后端的个人干货
  • 教你 Shiro + SpringBoot 整合 JWT
  • 教你 Shiro 整合 SpringBoot,避开各种坑

你的关注就是我不断发文最大的动力

转载于:https://www.cnblogs.com/HowieYuan/p/9394552.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/427292.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

python入口函数的作用_python之函数中参数的作用域

学编程究竟学的是什么呢&#xff1f;在写文章的这几天也一直在思考这个问题——恐怕这也是接下来的几年一直会去思考的问题。这个问题的答案也会指导我的方法论&#xff0c;所以索性整顿一下。 现阶段我的回答是&#xff0c;发现需求&#xff0c;然后解决。 最大的需求无非是完…

idea lombok 离线安装_Lombok与IntelliJ IDEA干了一架,完胜

我相信前段时间&#xff0c;有更新IDEA到2020.2版本的同学&#xff0c;在安装Lombok的过程中&#xff0c;肯定遇到与Lombok无法兼容的问题&#xff0c;并且报错&#xff1a;Caused by: com.intellij.psi.PsiInvalidElementAccessException: Element: class de.plushnikov.intel…

jquery设置宽_JavaScript学习笔记(三十二) jQuery(中)

jQuery昨天讲了 jQuery 的基本选择器筛选器和属性操作今天来说一些 jQuery 别的东西元素操作创建一个元素var div $()内部插入元素// 向 div 元素中插入一个 p 元素&#xff0c;放在最后$(div).append($())// 把 p 元素插入到 div 中去&#xff0c;放在最后$(hello).appendTo(…

python自动化框架测试实操_自动化框架之 python+selenium+pytest

1.概述 selenium&#xff1a; 基于JavaScript代码库的自动化测试框架&#xff0c;通过脚本语言&#xff0c;模拟用户行为操作&#xff0c;最接近用户真实场景&#xff0c;实现对web自动测试。 Selenium&#xff0c;是目前的最火爆企业最主流的webUI自动化框架 pytest: pytest是…

mysql 关联查询慢_mysql慢查询语句分析总结

我们经常会接触到MySQL&#xff0c;也经常会遇到一些MySQL的性能问题。我们可以借助慢查询日志和explain命令初步分析出SQL语句存在的性能问题通过SHOW FULL PROCESSLIST查看问题SHOW FULL PROCESSLIST相当于select * from information_schema.processlist可以列出正在运行的连…

python opencv3 轮廓检测

git&#xff1a;https://github.com/linyi0604/Computer-Vision 1 # coding:utf82 3 import cv24 import numpy as np5 6 # 创建一个200*200 的黑色空白图像7 img np.zeros((200, 200), dtypenp.uint8)8 # 在图像的中央位置 放置一个100*100的白色方块9 img[50:150, 50: 150] …

CentOS7搭建NTP服务器

搭建ntp服务器 查看服务器、客户端操作系统版本 2查看服务器是否安装ntp 3如果没有安装 4安装完成后重新查看服务器是否安装ntp 5查看ntp服务器状态 6修改配置文件 注释 #server 0.centos.pool.ntp.org iburst #server 1.centos.pool.ntp.org iburst #server 2.centos.p…

mysql 5.7.21-linux_MySQL 5.7.21 Linux平台安装 Part 2

从今天开始MySQL相关方面的东西今天是关于MySQL的安装系统为 redhat 6.10数据库为MySQL 5.7.21PS:目前最新版本为MySQL 5.7.251. 目录规划2. MySQL 5.7 下载目前MySQL 社区的GA 提供5 和8 的下载由于目前大多数系统用的还是5的版本这里统一使用5的版本注意&#xff0c;下面是按…

session很快失效_深夜,我偷听到程序员要对session下手...

我是一个web服务器我的工作是给人类提供上网服务&#xff0c;我每天要为数以万计的人提供网页浏览服务。已经是深夜了&#xff0c;我还在和手下几个兄弟为了一件事紧张讨论着。“老大&#xff0c;现在咱们每天处理的请求越来越多了&#xff0c;session同步的问题不能再拖了&…

centos7安装samba服务器

1查看是否安装samba服务 2如果为空则没有安装&#xff0c;安装显示安装完成即成功 3查看samba状态 4查看配置文件的位置 5配置文件备份&#xff0c;直接传输到本地备份 6修改配置文件 Path共享目录位置 Valid users 可以查看的用户 Browseable可以查看共享文件夹的目录&a…

微信小程序—day01

前言 听说谷歌准备回中国了&#xff0c;玩了一下谷歌刚入驻微信的小程序&#xff1a;“猜画小歌”&#xff0c;又一次见识到了ai的强大魅力。看来python之路&#xff0c;前途还是一片光明的。 因为18年初时的“跳一跳”&#xff0c;带火了微信小程序&#xff0c;一直想要写一个…

docker mysql 操作_[Docker] Docker 快速搭建本地MySQL开发环境

[Docker] Docker 快速搭建本地MySQL开发环境关于 Docker 的安装使用本文不再赘述&#xff0c;有兴趣的可以通过官网或是浏览我的专栏文章了解。今天着重给大家介绍下如何利用Docker快速搭建一个MySQL的环境&#xff0c;来协助本地开发。TOC手机用户请横屏获取最佳阅读体验&…

centos7安装DHCP服务器

1检查防火墙和selinux&#xff08;关闭&#xff09; 关闭防火墙和selinux&#xff0c;这边不多说 2检查DHCP状态 3安装DHCP软件包 4把系统默认的样例复制 5修改配置文件 option domain-name “example.org”; --DNS域名 option domain-name-servers ns1.example.org, ns2.…

python装饰器解析_Python 装饰器解析

一、装饰器定义&#xff1a;本质是函数&#xff0c;&#xff08;装饰其他函数&#xff09;就是为其他函数添加附加功能 二、装饰器的作用&#xff1a;在不改变原函数的情况下&#xff0c;为原函数前后添加新的功能 三、装饰器的原则&#xff1a; 1、不能修改被装饰的函数的源代…

centos7网卡识别不到,无法远程工具连接

这是在安装dhcp的时候遇到的问题&#xff0c;远程工具连接不上了。没IP地址没得玩 解决办法 直接在虚拟机打开终端&#xff0c;找到配置文件ifcfg-ens33 TYPE“Ethernet” PROXY_METHOD“none” BROWSER_ONLY“static”----原先的dhcp换成static BOOTPROTO“no” DEFROUTE“ye…

python爬虫脚本ie=utf-8_Python反爬虫伪装浏览器进行爬虫

对于爬虫中部分网站设置了请求次数过多后会封杀ip&#xff0c;现在模拟浏览器进行爬虫&#xff0c;也就是说让服务器认识到访问他的是真正的浏览器而不是机器操作 简单的直接添加请求头&#xff0c;将浏览器的信息在请求数据时传入&#xff1a; 打开浏览器--打开开发者模式--请…

centos7加入第二块网卡无法识别

本来是做DHCP服务加一块网卡的&#xff0c;然后识别不出来&#xff0c;没得玩 centos7加入第二块网卡无法识别 1发现只有一块网卡 2使用nmcli con show命令&#xff0c;查看网卡的UUID信息&#xff0c;记下UUID值 3启动networkManager’ 4识别 5使用ip addr命令查看网卡信息…

hbase 导入mysql_HBase导入SQL Server数据库数据

在先前的几篇随笔中已经介绍了Hadoop、Zookeeper、Hbase的分布式框架搭建方案&#xff0c;目前已经搭建完成了一个包含11个节点的分布式集群。而对于HBase数据库的使用仅限于测试性质的增删改查指令&#xff0c;为了进一步熟悉分布式框架的使用&#xff0c;本文介绍将已有的数据…

googlenet网络结构_CNN网络结构的发展

 CNN基本部件介绍&#xff0c;1. 局部感受野在图像中局部像素之间的联系较为紧密&#xff0c;而距离较远的像素联系相对较弱。因此&#xff0c;其实每个神经元没必要对图像全局进行感知&#xff0c;只需要感知局部信息&#xff0c;然后在更高层局部信息综合起来即可得到全局信…

linux数据库mysql的安装

1数据库文件放到opt下面 2赋予权限775 3运行脚本 4运行成功 5数据库操作 密码修改并刷新 权限修改&#xff0c;允许外部设备访问 6工具连接 7附录 1、显示当前数据库服务器中的数据库列表&#xff1a;   mysql> SHOW DATABASES;   2、建立数据库&#xff1a;   …