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

优效学院,大数据,人工智能,Java,架构在线教育

文末领取资料


Java 8 发布至今也已经好几年过去,如今 Java 也已经向 11 迈去,但是 Java 8 作出的改变可以说是革命性的,影响足够深远,学习 Java 8 应该是 Java 开发者的必修课。

今天给大家带来 Java 8 Stream 讲解,为什么直接讲这个,是因为只要你学完,立刻就能上手,并能让它在你的代码中大展身手。

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

本篇文章主要内容:

介绍 Stream 以及 Stream 是如何处理集合的

介绍 Stream 与集合的关系与区别

Stream 的基本方法介绍

一. 什么是 Stream

Stream 中文称为 “流”,通过将集合转换为这么一种叫做 “流” 的元素序列,通过声明性方式,能够对集合中的每个元素进行一系列并行或串行的流水线操作。

换句话说,你只需要告诉流你的要求,流便会在背后自行根据要求对元素进行处理,而你只需要 “坐享其成”。

二. 流操作


整个流操作就是一条流水线,将元素放在流水线上一个个地进行处理。

其中数据源便是原始集合,然后将如 List的集合转换为 Stream类型的流,并对流进行一系列的中间操作,比如过滤保留部分元素、对元素进行排序、类型转换等;最后再进行一个终端操作,可以把 Stream 转换回集合类型,也可以直接对其中的各个元素进行处理,比如打印、比如计算总数、计算最大值等等

很重要的一点是,很多流操作本身就会返回一个流,所以多个操作可以直接连接起来,我们来看看一条 Stream 操作的代码:


如果是以前,进行这么一系列操作,你需要做个迭代器或者 foreach 循环,然后遍历,一步步地亲力亲为地去完成这些操作;但是如果使用流,你便可以直接声明式地下指令,流会帮你完成这些操作。

有没有想到什么类似的?是的,就像 SQL 语句一样, select username from user where id = 1,你只要说明:“我需要 id 是 1 (id = 1)的用户(user)的用户名(username )”,那么就可以得到自己想要的数据,而不需要自己亲自去数据库里面循环遍历查找。

三. 流与集合

什么时候计算

Stream 和集合的其中一个差异在于什么时候进行计算。

一个集合,它会包含当前数据结构中所有的值,你可以随时增删,但是集合里面的元素毫无疑问地都是已经计算好了的。

流则是按需计算,按照使用者的需要计算数据,你可以想象我们通过搜索引擎进行搜索,搜索出来的条目并不是全部呈现出来的,而且先显示最符合的前 10 条或者前 20 条,只有在点击 “下一页” 的时候,才会再输出新的 10 条。

再比方在线观看电影和你硬盘里面的电影,也是差不多的道理。

外部迭代和内部迭代

Stream 和集合的另一个差异在于迭代。

我们可以把集合比作一个工厂的仓库,一开始工厂比较落后,要对货物作什么修改,只能工人亲自走进仓库对货物进行处理,有时候还要将处理后的货物放到一个新的仓库里面。在这个时期,我们需要亲自去做迭代,一个个地找到需要的货物,并进行处理,这叫做外部迭代。

后来工厂发展了起来,配备了流水线作业,只要根据需求设计出相应的流水线,然后工人只要把货物放到流水线上,就可以等着接收成果了,而且流水线还可以根据要求直接把货物输送到相应的仓库。

这就叫做内部迭代,流水线已经帮你把迭代给完成了,你只需要说要干什么就可以了(即设计出合理的流水线)。

Java 8 引入 Stream 很大程度是因为,流的内部迭代可以自动选择一种合适你硬件的数据表示和并行实现;而以往程序员自己进行 foreach 之类的时候,则需要自己去管理并行等问题。

一次性的流

流和迭代器类似,只能迭代一次。

Stream stream = list.stream().map(Person::getName).sorted().limit(10); 

List newList = stream.collect(toList());

List newList2 = stream.collect(toList());

四. 方法介绍,开始实战

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

List 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

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 newlist = 

list.stream().map(Person::getName).collect(toList());

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

8. flatMap(T -> Stream)

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

List 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,因为 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 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);

资料地址

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 195,719评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,337评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 142,887评论 0 324
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,488评论 1 266
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,313评论 4 357
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,284评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,672评论 3 386
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,346评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,644评论 1 293
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,700评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,457评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,316评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,706评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,261评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,648评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,859评论 2 335

推荐阅读更多精彩内容