文档章节

Java8 Stream API使用

青离
 青离
发布于 2017/11/14 16:25
字数 3002
阅读 519
收藏 66
点赞 0
评论 2

Java8面世后,目前工作中使用最多的特性就当是Stream API。Stream API结合lamada表达式带来的是全新的编程体验,以往一些繁琐的数据处理,如今不仅条理清晰,而且代码量至少减少了一半。Stream API使用一段时间后,我就不满足于零碎的一些常用组合方式,希望对Stream有一个轮廓性的认识。因而在参考了官方文档和众多网上资料后,自己对Stream API做了一些小总结,以期达到梳理和记录之用。

1.认识Stream

Java8对Stream的定义是这样的

A sequence of elements supporting sequential and parallel aggregate operations.

简单的翻译就是支持顺序和并行的汇聚操作的一组元素

然而这个解释看上去没有什么用,更通俗的说法是Stream是一个高级的Iterator,相对于原始的Iterator显示地遍历元素并执行操作,Stream在其内部隐式地进行数据转换。

另外Stream也可以理解成一个管道,它并非数据结构,不保存数据,input的数据从管道一头进入,管道中完成定义好的处理方式,从另一头输出你要的结果。因而Stream是单向的,不可往复的,想要多次使用只有创建新的Stream。

Stream的处理过程可分为三个部分,我们举个例子,对1,2,3三个数的平方求和。

List<Integer> numbers = Arrays.asList(1, 2, 3);
numbers.stream().mapToInt(n -> n * n).sum();

以上的操作包含了Stream的三部分,分别是创建Stream转换Stream聚合Stream

下面就依次分析下这三个部分

2.创建Stream

常用的创建Stream的方式有三种:

  1. 通过集合类的相关方法
  2. 通过Stream接口的静态工厂方法
  3. 其他能产生流的类的方法

2.1 集合类的相关方法

Collection接口中定义了一个stream方法

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

因而所有Collection的子类都能通过此方法开启Stream,常见的就是从数据库查询出一个List,然后使用Stream进行处理。

List<User> userList = // 数据库查询
long teenagerNum = userList.stream().filter(u -> u.getAge() < 20).count();

从数据库查询出一组User,获得出年龄小于20岁的用户数。

还有一种情况是,参数是一个数组,可以使用Arrays工具类来处理

int[] nums = {1, 2, 3, 2};
Arrays.stream(nums).distinct().forEach(System.out::println);

distinct方法用来去重,即将一组整数去重后打印出来

2.2 Stream静态工厂方法

Stream接口中也提供多种静态方法来辅助我们构造Stream

1.of方法,非常好用的方法,接受可变参数

Stream<String> stringStream = Stream.of("1", "2", "3");

或者直接传入数组

String[] strs = {"1", "2", "3"};
Stream<String> stringStream = Stream.of(strs);

2.generator方法,生成一个无限长度的Stream,需要一个自定义的Supplier类,比如创建一个随机数的Stream

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

可以使用lamada表达式及Method Reference简化

Stream.generate(() -> Math.random());
Stream.generate(Math::random);

一般无限长度的Stream都会配合Stream的limit方法来使用

3.iterate方法,同样生成无限长度的Stream,但它是对给定的种子(seed)反复调用用户指定函数来生成,其生成的元素可认为是:seed,f(seed),f(f(seed))无限循环

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

4.concat方法,将两个Stream组合成一个Stream

Stream<String> stream1 = Stream.of("1", "2", "3");
Stream<String> stream2 = Stream.of("4", "5");
Stream.concat(stream1, stream2);

5.builder方法,使用追加的方式建立Stream然后消费

Stream.builder()
    .add("1")
    .add("2")
    .build();

2.3 其他方式

Java也在其他可能有Stream操作的类中增加了便捷的方法

// 文件读取
java.io.BufferedReader.lines()
// 文件遍历
java.nio.file.Files.walk()
// 随机数
java.util.Random.ints()
// 正则匹配
Pattern.splitAsStream(java.lang.CharSequence)

3.转换Stream

转换Stream就是把一个Stream通过某些行为转换成新的Stream。Stream接口中定义了常用的几个转换方法,但在实际使用中确实大大的方便。

3.1 distinct

对于Stream中包含的元素进行去重(依赖元素的equals方法)

之前对一个List进行去重,都是借助HashSet来做

List list2 = new ArrayList(new HashSet(list));

现在也可以使用distinct来做

List list2 = list.stream().distinct().collect(Collectors.toList());

3.2 filter

对Stream中包含的元素使用给定的过滤函数进行过滤,新生成的Stream只包含符合条件的元素

List<Integer> numbers = Arrays.asList(-1, 1, 0);
numbers.stream().filter(n -> n > 0).collect(Collectors.counting());

3.3 map

对Stream中包含的元素使用给定的转换函数进行转换,新生成的Stream只包含转换生成的元素

Stream.of(1, 2, 3).map(n -> n + 1).collect(Collectors.toList());

这个方法由三个对于原始数据类型的变种方法,分别是mapToIntmapToLong, mapToDouble, 主要是减少自动装箱和拆箱的性能消耗。

3.4 flatMap

和map相似,但一般用来将多层级扁平化,举个例子大家就清楚了。

有个两层结构的数据结构

[ [1, 2, 3], [4, 5], [6] ]

把它全部摊平

[1, 2, 3, 4, 5, 6]

就可以使用flatMap

Stream<List<Integer>> intStream = Stream.of(Arrays.asList(1, 2, 3),Arrays.asList(4,5),Arrays.asList(6));
        intStream.flatMap(childList -> childList.stream()).forEach(System.out::println);

3.5 peek

生成一个包含原Stream所有元素的新Stream,同时提供一个消费函数(Consume),当Stream中每个元素被消费时都会执行给定的消费函数。

举个例子来说,对1到9求和,求和前打印所有求和元素。因为Stream是单向的,做了求和就不能再打印了,怎么办呢?用peek方法。

int sum = IntStream.range(1, 10).peek(System.out::println).sum();
System.out.println("sum:" + sum);

3.6 limit

对Stream进行截断操作,获取其前N个元素,如果原Stream中包含元素个数小于N,就获取其所有元素。

IntStream.range(1, 10).limit(5).forEach(System.out::println);

3.7 skip

丢弃Stream前N个元素,返回剩下元素组成的新Stream,如果原Stream中包含的元素个数小于N,返回空Stream。

比如打印一个数组的第10到20个元素

IntStream.range(1, 100).skip(10).limit(10).forEach(System.out::println);

3.8 sorted

对Stream中的元素按默认方式排序或者指定比较器排序

Stream.of(1, 3, 4 ,2).sorted().forEach(System.out::println);

4.聚合Stream

聚合操作接受一个元素序列作为输入,反复使用某个合并操作,把序列中的元素合并成一个汇总的结果。比如求元素的总和或最大值,或者把元素累积成一个List对象。Stream接口有一些通用的汇聚操作,比如reduce和collect;也有特定用途的汇聚操作,比如sum,max,count。

4.1 collect

将Stream中的元素收集到一个结果容器中。java8还为collect方法提供了工具类Collectors,可以方便地生成List,Set,Map等集合。

// 获取userId的List
List<Long> userIdList = users.stream().map(u -> u.getId()).collect(Collectors.toList());
// 获取userId的Set
Set<Long> userIdSet = users.stream().map(u -> u.getId()).collect(Collectors.toSet());
// 获取userId的Map
Map<Long, User> userIdMap = users.stream().collect(Collectors.toMap(u -> u.getId(), u -> u));

另外Collectors还提供了两种非常常用的聚合方式:分组和分片,分别对应groupingBy和partitioningBy两个方法。怎么用呢,我们来看例子。

我想将用户按所在地址分组,北京的放在一起,上海的放在一起,可以用groupingBy。

Map<String, List<User>> userAddressMap = users.stream().collect(Collectors.groupingBy(User::getAddress));

或者我想将用户按年龄区分,20岁以上为一个分片,20岁以下为一个分片,可以用partitioningBy。

Map<Boolean, List<User>> userAgeMap = users.stream().collect(Collectors.partitioningBy(u -> u.getAge() > 20));

这两个的区别也就显而易见了,groupingBy是按照某个内部字段进行分组,而partitioningBy是按照某个条件将元素分成是和非两个分片。

Collectors中还有其他很便捷的方法,有兴趣的可以研究下。

4.2 reduce

上面说的collect可以看成对Stream元素进行不同方式的聚集,而reduce则是对Stream元素进行指定方式的聚合。

比如我想求用户的最大年龄,可以使用reduce来操作。

Optional optional = users.stream().map(User::getAge).reduce((a1, a2) -> a1 > a2 ? a1 : a2);

这个方法返回值是Optional,它是java8防止出现空指针的一种方式。要获得最大的年龄,调用Optional的get方法即可。

Integer maxAge = users.stream().map(User::getAge).reduce((a1, a2) -> a1 > a2 ? a1 : a2).get();

但是这种方式还是可能会出现空指针,我们可以在reduce时设置一个默认值。

Integer maxAge = users.stream().map(User::getAge).reduce(0 ,(a1, a2) -> a1 > a2 ? a1 : a2);

这样就不需要Optional做一次转换了。

像最大,最小,计数这些是常见的计算方式,在Stream中也都提供了直接的方法供我们使用。

  • max(Comparator comp)
  • min(Comparator comp)
  • count()

对于max,min方法需要提供Compartor比较器。如果是在诸如mapToInt,mapToLong,mapToDouble之类的基础数据操作后,则不用提供比较器,另外还支持求和(sum)和平均(average)方法。

users.stream().mapToInt(User::getAge).sum();
users.stream().mapToInt(User::getAge).average();

4.3 搜索相关

Stream API还提供了几种快捷的搜索方法,支持在一组元素中的常用搜索。

  • allMatch:所有元素都满足匹配条件
  • anyMatch:任一元素满足匹配条件
  • findFirst:返回Stream中的第一个元素
  • findAny:返回Stream中的任意个元素
  • noneMatch:所有元素都不满足匹配条件

4.4 遍历

将Stream中的元素逐个按照指定方式进行消费。

打印所有用户

users.stream().forEach(System.out::println);

5.Stream API的优点

  1. Stream转换操作是惰性化的(lazy),即多次转换操作在聚合操作时只需一次循环就能完成,并不会因为多次转换造成额外的循环开销。
  2. 因为1的原因,Stream的数据源可以是无限的,它并不会像普通iterator一样需要把所有数据都加载到内存中。比如Stream的generate方法和limit方法相结合,可以从无限的数据源中操作自己想要的数据。
  3. Stream可以并行化操作,它依赖于java7中引入的Fork/Join框架来拆分任务和加速处理过程。对于Collection子类,直接使用parallelStream方法即可开启并行化操作。不过并行化也是会有额外的开销的,因此要适当地使用。

6.Stream API用例

以库存实体Stock为例

public class Stock {

    // 主键id
    Long id;
    // 商品id
    Long skuId;
    // 供应商id
    int supplierId;
    // 状态(0:不可用,1:可用,2:任务中)
    int status;
    // 库存量
    BigDecimal amount;
}

一般都是根据条件从数据库查询出一个Stock的List,变量名为stockList,从这个List出发,介绍一些常用的Stream的用例。

6.1 从大集合中获取小集合

// 获取id的集合
List<Long> idList = stockList.stream().map(Stock::getId).collect(Collectors.toList());
// 获取skuid集合并去重
List<Long> skuIdList = stockList.stream().map(Stock::getSkuId).distinct().collect(Collectors.toList());
// 获取supplierId集合(supplierId的类型为int,返回List<Integer>,使用boxed方法装箱)
Set<Integer> supplierIdSet = stockList.stream().mapToInt(Stock::getSupplierId).boxed().collect(Collectors.toSet());

6.2 分组与分片

// 按skuid分组
Map<Long, List<Stock>> skuIdStockMap = stockList.stream().collect(Collectors.groupingBy(Stock::getSkuId));
// 过滤supplierId=1然后按skuId分组
Map<Long, List<Stock>> filterSkuIdStockMap = stockList.stream().filter(s -> s.getSupplierId() == 1).collect(Collectors.groupingBy(Stock::getSkuId));
// 按状态分为不可用和其他两个分片
Map<Boolean, List<Stock>> partitionStockMap = stockList.stream().collect(Collectors.partitioningBy(s -> s.getStatus() == 0));

6.3 计数与求和

// 统计skuId=1的记录数
long skuIdRecordNum = stockList.stream().filter(s -> s.getSkuId() == 1).count();
// 统计skuId=1的总库存量
BigDecimal skuIdAmountSum = stockList.stream().filter(s -> s.getSkuId() == 1).map(Stock::getAmount).reduce(BigDecimal.ZERO, BigDecimal::add);

6.4 特定用法

// 多重分组并排序,先按supplierId分组,再按skuId分组,排序规则,先supplierId后skuId
Map<Integer, Map<Long, List<Stock>>> supplierSkuStockMap = stockList.stream().collect(Collectors.groupingBy(Stock::getSupplierId, TreeMap::new,
                Collectors.groupingBy(Stock::getSkuId, TreeMap::new, Collectors.toList())));

// 多条件排序,先按supplierId正序排,再按skuId倒序排
// (非stream方法,而是集合的sort方法,直接改变原集合元素,使用Function参数)
stockList.sort(Comparator.comparing(Stock::getSupplierId)
                .thenComparing(Stock::getSkuId, Comparator.reverseOrder()));

参考文档

http://ifeve.com/stream/ (强烈推荐,非常清晰的介绍)

http://www.infoq.com/cn/articles/java8-new-features-new-stream-api

https://www.ibm.com/developerworks/cn/java/j-lo-java8streamapi/

© 著作权归作者所有

共有 人打赏支持
青离
粉丝 263
博文 47
码字总数 104472
作品 0
海淀
后端工程师
加载中

评论(2)

如梦技术
如梦技术
不错,全面
wonderWang
wonderWang
不错,总结的挺全面的
Java系列 – 用Java8新特性进行Java开发太爽了(续)

本人博客文章网址:https://www.peretang.com/using-java8s-new-features-to-coding-is-awesome-2/ 前言 上周, 我们谈论了关于Java8的新特性有那些, 什么是函数式编程, 什么是Lambda表达式, 这...

PereTang ⋅ 2017/07/18 ⋅ 0

Java8中的简易并发

Java8中的简易并发 分享到: 5 本文由 ImportNew - kingviker 翻译自 jaxenter。欢迎加入Java小组。转载请参见文章末尾的要求。 有人曾经说过(很抱歉,我们找不到原句了): 初级程序员认为...

longbadx ⋅ 2014/06/03 ⋅ 0

【java8】java新特性(一)——全局观

一、前言 年前的时候 ,我一个师姐出去工作,被鄙视了。说写的代码太垃圾。当时我也没有在意,回头想想自己,本以为自己写的代码天衣无缝,无可挑剔。但是自从自己遇到了Java8 后,我的世界观...

kisscatforever ⋅ 03/15 ⋅ 0

跟上Java8 - Stream API快速入门

跟上Java8 - Stream API快速入门 王爵的技术博客2017-07-1821 阅读 streamfilterJava8 在前面我们简单介绍了 表达式,Java8旨在帮助程序员写出更好的代码, 其对核心类库的改进也是关键的一部...

王爵的技术博客 ⋅ 2017/07/18 ⋅ 0

Java8-Stream-创建流

友情提示:如果要看懂Stream,得先把Lambda先搞明白,可以去Java8-Lambda表达式瞧一瞧 什么是Stream Stream 是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行...

特拉仔 ⋅ 02/24 ⋅ 0

Java8解决了什么?

在学习面向对象时,许多人都会用Java来举例子,但是其实Java并非纯正的面向对象语言,最明显的就是:int,double等基本类型不是对象。 自从java8出来过后,引入了流,函数式编程,就更不是在向...

MageekChiu ⋅ 2017/08/02 ⋅ 0

Java 8新特性(二)

集合类的批处理: Java8除了Lambda表达式外还提供了另一个重要的特性,即集合的批处理操作,集合类的批处理操作API的目的是实现集合类的“内部迭代”,并期望充分利用现代多核CPU进行并行计算...

casoc ⋅ 2015/06/02 ⋅ 0

Java8 学习笔记

先来一个概览,上图是我整理的Java8中的新特性,总的来看,大致上可以分成这么几个大块。 函数式接口 所谓的函数式接口就是只有一个抽象方法的接口,注意这里说的是抽象方法,因为Java8中加入...

xrzs ⋅ 2013/05/14 ⋅ 0

学习Java8 中的 Stream(一)

不想再用for嵌套for操作了,java8 带来了新的API —— Stream,非常强大! Stream中文翻译成流,是一个支持串行和并行操作元素的序列,也是Lambda表达式配合使用的强大工具。 源码在java.ut...

MaxZing ⋅ 01/21 ⋅ 0

如何理解Java8 Stream API是拉,Reactor是推

在Java8 Stream API里面,如果只有中间操作,没有终止操作。数据是不会进行处理的。 比如说,下面代码不会输出的 但是,在Reactor中,为什么如下代码也没有执行 Java8 Stream是只有碰到终止操...

西夏一品堂 ⋅ 2017/10/13 ⋅ 1

没有更多内容

加载失败,请刷新页面

加载更多

下一页

PHP语言系统ZBLOG或许无法重现月光博客的闪耀历史[图]

最近在写博客,希望通过自己努力打造一个优秀的教育类主题博客,名动江湖,但是问题来了,现在写博客还有前途吗?面对强大的自媒体站点围剿,还有信心和可能型吗? 至于程序部分,我选择了P...

原创小博客 ⋅ 13分钟前 ⋅ 0

IntelliJ IDEA 2018.1新特性

工欲善其事必先利其器,如果有一款IDE可以让你更高效地专注于开发以及源码阅读,为什么不试一试? 本文转载自:netty技术内幕 3月27日,jetbrains正式发布期待已久的IntelliJ IDEA 2018.1,再...

Romane ⋅ 39分钟前 ⋅ 0

浅谈设计模式之工厂模式

工厂模式(Factory Pattern)是 Java 中最常用的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。 在工厂模式中,我们在创建对象时不会对客户端暴露创建逻...

佛系程序猿灬 ⋅ 今天 ⋅ 0

Dockerfile基础命令总结

FROM 指定使用的基础base image FROM scratch # 制作base image ,不使用任何基础imageFROM centos # 使用base imageFROM ubuntu:14.04 尽量使用官方的base image,为了安全 LABEL 描述作...

ExtreU ⋅ 昨天 ⋅ 0

存储,对比私有云和公有云的不同

导读 说起公共存储,很难不与后网络公司时代的选择性外包联系起来,但尽管如此,它还是具备着简单和固有的可用性。公共存储的名字听起来也缺乏专有性,很像是把东西直接堆放在那里而不会得到...

问题终结者 ⋅ 昨天 ⋅ 0

C++难点解析之const修饰符

C++难点解析之const修饰符 c++ 相比于其他编程语言,可能是最为难掌握,概念最为复杂的。结合自己平时的C++使用经验,这里将会列举出一些常见的难点并给出相应的解释。 const修饰符 const在c...

jackie8tao ⋅ 昨天 ⋅ 0

聊聊spring cloud netflix的HystrixCommands

序 本文主要研究一下spring cloud netflix的HystrixCommands。 maven <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-clo......

go4it ⋅ 昨天 ⋅ 0

Confluence 6 从其他备份中恢复数据

一般来说,Confluence 数据库可以从 Administration Console 或者 Confluence Setup Wizard 中进行恢复。 如果你在恢复压缩的 XML 备份的时候遇到了问题,你还是可以对整个站点进行恢复的,如...

honeymose ⋅ 昨天 ⋅ 0

myeclipse10 快速搭建spring boot开发环境(入门)

1.创建一个maven的web项目 注意上面标红的部分记得选上 2.创建的maven目录结构,有缺失的目录可以自己建立目录补充 补充后 这时候一个maven的web项目创建完成 3.配置pom.xml配置文件 <proje...

小海bug ⋅ 昨天 ⋅ 0

nginx.conf

=========================================================================== nginx.conf =========================================================================== user nobody; #......

A__17 ⋅ 昨天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部