文档章节

2、Stream API

MiaoXG
 MiaoXG
发布于 2016/06/22 10:25
字数 2224
阅读 43
收藏 1

2.1 概述

Steam API 是 Java 8 中全新的特性,它基于Lambda 表达式,对集合(Collection)的各种操作进行了大幅度的增强,极大的提高了编码的效率和程序的可读性。

Stream API 提供串行和并行两种模式,并行模式会自动创建多个线程,使用 fork/join(Java 7 特性) 并行方式来拆分任务和加速处理过程,能充分利用多核处理器。牛X的是,我们并不需要手动写多线程处理的代码,Stream API 自行实现了高性能的并发程序。

eg,看一下下面这段代码干了什么?然后想一下如果不使用 Stream 操作,应该怎么实现?

Set<String> zoneIds = ZoneId.getAvailableZoneIds();
zoneIds.stream().filter(zoneId -> zoneId.startsWith("Asia")).skip(10).limit(5).forEach(System.out::println);

2.1.1 什么是 Stream

这里的 Stream 跟 IO流等其它流并不是指同样的东西,而是对集合执行流式操作的一种抽象。要分清一点,Stream 不是数据结构,本身不存储元素,元素可能存储在底层的集合中,或根据需要产生出来。

2.1.2 如何使用

Stream API 的用法很简单,分3步执行,即: 创建/获取流 -> 中间操作(过滤、转换等) -> 终止操作( 聚合、收集结果)

其中, 中间操作可以执行多次,并且都是延迟执行的,每次操作原有的 Stream 对象不改变,会产生一个新的 Stream; 终止操作只能有一个,一旦执行完成,Stream 就没了。

2.2 创建流

2.2.1 从集合创建

Java 8 中,Collection 接口增加了 stream 方法,所以我们可以把任何一个集合转成Stream

Set<String> zoneIds = ZoneId.getAvailableZoneIds();
Stream<String> demo = zoneIds.stream();

2.2.2 从数组创建

Arrays 类中增加了 stream(T[] array)方法

String[] arr = {"1"};
Stream<String> demo = Arrays.stream(arr);

2.2.3 从静态方法创建

Stream.of(T... values) Stream.generate(Supplier<T> s) 生成无限流 Stream.iterate(final T seed, final UnaryOperator<T> f), 生成无限流,但是多了一个seed,生成的流是 seed, f(seed), f(f(seed))...

Stream.of("1", "2", "2", "5", "7", "5", "6", "3", "4", "3");

Stream<Integer> stream1 = Stream.generate(() -> RandomUtils.nextInt(1, 10));
stream1.limit(10).forEach(System.out::print);  // 输出 2223915594

Stream<Integer> stream2 = Stream.iterate(1, x -> x + 1);
stream2.limit(10).forEach(System.out::print); // 12345678910

2.2.4 其它

  • java.io.BufferedReader.lines()
  • java.util.stream.IntStream.range()
  • java.nio.file.Files.walk()
  • java.util.Spliterator
  • Random.ints()
  • BitSet.stream()
  • Pattern.splitAsStream(java.lang.CharSequence)
  • JarFile.stream()

2.3 中间操作

2.3.1 过滤

  • filter 流中所有符合条件的数据转到新流。filter方法的参数是一个 Pridicate<? super T> 类型的函数式接口,即一个返回 boolean 类型的 Lambda表达式。
Set<String> zoneIds = ZoneId.getAvailableZoneIds();
Stream<String> oldStream = zoneIds.stream();
Stream<String> newStream = oldStream.filter(zoneId -> zoneId.startsWith("Asia"));

2.3.2 转换

  • map 流中所有数据,经过处理后转到新流。map方法的参数是一个 Function<? super T, ? extends R> 类型的函数式借口,即接收一个参数并返回处理后的结果。 另,flatMap 方法会展开转换的元素。
Set<String> zoneIds = ZoneId.getAvailableZoneIds();
Stream<String> oldStream = zoneIds.stream();
Stream<String> newStream = oldStream.map(zoneId -> zoneId.replace("//", "-"));

2.3.3 提取

获取一个流的一部分。

Set<String> zoneIds = ZoneId.getAvailableZoneIds();
zoneIds.stream().skip(10).limit(5);
// 另一个较好的例子
Stream<Integer> limit = Stream.generate(() -> RandomUtils.nextInt(1, 10)).limit(100);

2.3.4 组合

将两个流合并成一个

Stream.concat(Stream.of("1", "2"), Stream.of("4", "5", "6"));

2.3.5 其他

  • distinct 去重
  • sorted 排序
  • peek 监听。可以指定一个函数,当元素被处理的时候被每个元素调用
  • parallel 转为并行流
  • sequential 转为串行流
  • unordered 不知道
Stream.of("1", "2", "2", "5", "7", "5", "6", "3", "4", "3")
        .peek(x -> System.out.println(x+" "))
        .filter(x -> x.compareTo("3")>0)
        .map(x -> x.replace("5", "9"))
        .distinct()
        .sorted()
        .skip(1)
        .limit(5)
        .forEach(System.out::print);

最终结果:

1 
2 
2 
5 
7 
5 
6 
3 
4 
3 
679

2.3.6 小结

思考一个疑问,我们对一个 Stream 进行多次转换操作,每次都会产生一个新的 Stream,这样操作是不是比我们自己写单次循环耗费更多的时间?

不是,转换操作都是lazy的,多个转换操作只会在终止操作的时候融合起来,一次循环完成。

2.4 终止操作

这部分是 Stream API 中最重要的内容,从上文中我们已经知道如何创建一个流以及如何对其进行转换。现在,我们要从流中获取结果。

2.4.1 聚合操作

  • reduce 这是一个通用的聚合方法,最基本的 reduce 方法接收一个 BinaryOperator<T> 类型的参数,使用的方式是这个样子的:
IntStream is = IntStream.of(1, 2, 3 , 4, 5);
System.out.println(is.reduce((a, b) -> a+b));  // 输出 OptionalInt[15]

在这个操作里可以看到,reduce 方法有两个参数。其中第1个参数是上次操作返回的结果,第2个参数是 Stream 中下一个元素。如果是第一次执行,第1个参数是 Stream 中的第一个元素, 第2个参数是Stream 中的第2个元素。简单描述一下这个操作的执行过程,

操作 结果
首先流初始化后有5个元素 1, 2, 3, 4, 5
第1次聚合 3,3, 4, 5
第2次聚合 6, 4, 5
第3次聚合 10, 5
第4次聚合 15

reduce方法还有一个很常用的变种,接收两个参数: T reduce(T identity, BinaryOperator<T> accumulator),相对上面已经介绍过方法的多了一个参数:它允许用户提供一个值,如果Stream为空,就直接返回该值。

IntStream is = IntStream.of();
System.out.println(is.reduce(5, (a, b) -> a+b));  // 输出 5

2.4.2 聚合方法

很多情况下,我们所做的聚合操作都是一样的。所以为了方便,Stream API 直接提供了常用的聚合方法

  • sum
IntStream s = IntStream.of(1,2,3,4);
System.out.println(s.sum());

注:只有 IntStream、LongStream、DoubleStream 才有 sum 方法

  • count、max、min
System.out.println(ZoneId.getAvailableZoneIds().stream().count());   // 输出 590
System.out.println(ZoneId.getAvailableZoneIds().stream().min(String::compareTo));  // 输出 Optional[Africa/Abidjan]
System.out.println(ZoneId.getAvailableZoneIds().stream().max(String::compareTo));  // 输出 Optional[Zulu]
  • findFirst、findAny
System.out.println(ZoneId.getAvailableZoneIds().stream().findFirst());  // Optional[Asia/Aden]
System.out.println(ZoneId.getAvailableZoneIds().stream().findAny());    // Optional[Asia/Aden]
  • anyMatch、 allMatch、 noneMatch allMatch:Stream 中全部元素符合传入的 predicate,返回 true anyMatch:Stream 中只要有一个元素符合传入的 predicate,返回 true noneMatch:Stream 中没有一个元素符合传入的 predicate,返回 true
System.out.println(ZoneId.getAvailableZoneIds().stream().allMatch(x -> x.startsWith("A")));   // false
System.out.println(ZoneId.getAvailableZoneIds().stream().anyMatch(x -> x.startsWith("A")));   // true
System.out.println(ZoneId.getAvailableZoneIds().stream().noneMatch(x -> x.startsWith("A")));  // false

2.4.3 收集结果

  • collect 把结果收集到容器中,collect 方法的基本方法是这个
<R> R collect(Supplier<R> supplier,
              BiConsumer<R, ? super T> accumulator,
              BiConsumer<R, R> combiner);

但是这个方法用着比较麻烦, 一般直接用另外的工具方法

  • 收集到集合
List<String> list = ZoneId.getAvailableZoneIds().stream().limit(5).collect(Collectors.toList());
Set<String> set = ZoneId.getAvailableZoneIds().stream().collect(Collectors.toSet());
  • 收集到Map
Map<String, String> map = ZoneId.getAvailableZoneIds().stream()
        .limit(3)
        .collect(Collectors.toMap(x -> x, y -> y));
System.out.println(map);  
// 输出 {Asia/Aden=Asia/Aden, America/Cuiaba=America/Cuiaba, Etc/GMT+9=Etc/GMT+9}

2.4.5 遍历结果

并不是所有时候我们都需要把结果汇聚在一起,我们也可以只是遍历各个元素

  • iterator 转成传统的迭代器
Stream<String> zoneIdStream = ZoneId.getAvailableZoneIds().stream();
Iterator<String> it = zoneIdStream.iterator();
  • forEach、 forEachOrdered
ZoneId.getAvailableZoneIds().stream().limit(5).forEach(System.out::print);

2.4.6 分组

  • groupingBy 基本的分组方法描述是这样的,第一个参数是分组函数,第二个参数是返回的类型,第三个参数是一个收集器
public static <T, K, D, A, M extends Map<K, D>>
Collector<T, ?, M> groupingBy(Function<? super T, ? extends K> classifier,
                              Supplier<M> mapFactory,
                              Collector<? super T, A, D> downstream) 

最简单的用法是只提供一个分组函数,那么默认会返回

Map<String, List<String>> map = ZoneId.getAvailableZoneIds().stream()
        .limit(5)
        .collect(Collectors.groupingBy(x -> x.substring(0, x.indexOf("/")))
);
System.out.println(map);
// 输出 {Etc=[Etc/GMT+9, Etc/GMT+8], Asia=[Asia/Aden], Africa=[Africa/Nairobi], America=[America/Cuiaba]}

如果我们想控制类型,可以用Collectors改变,比如

Map<String, Set<String>> map = ZoneId.getAvailableZoneIds().stream()
        .limit(5)
        .collect(Collectors.groupingBy(x -> x.substring(0, x.indexOf("/")), Collectors.toSet())
);

Stream Api 还提供其他一些收集器,比如counting,计算每个分组的元素数量

Map<String, Long> map = ZoneId.getAvailableZoneIds().stream()
        .limit(5)
        .collect(Collectors.groupingBy(x -> x.substring(0, x.indexOf("/")), Collectors.counting()));
System.out.println(map);
// 输出 {Etc=2, Asia=1, Africa=1, America=1}

2.4.7 Optional 类型

在前文的介绍中,你应该看到了 Optional 这个东西,有很多终止操作的结果是Optional<T>。那么,Optional是什么? Optional 是一个容器,它可能包含也可能不包含一个值,用意是替代不安全的返回类型 NULL。比如,我们常见的一种代码,

public Integer getInteger() {
    return null;
}

如果我们需要使用这个方法,一般是这样的

Integer i = getSomething();
if(i != null) {
    System.out.println(i);
    // do something
}

调用一个返回T类型的方法后,为了安全我们必须验证这个对象不是 NULL,造成代码的阅读星和维护性都比较差,但是如果没有这样做,就很可能出现 NullPointException。 Optional 就是为了解决这个问题而设计的,它提供了一个接受 Consumer<? super T>类型参数的方法 ifPresent,只有当存在可用的元素时,才执行逻辑

public Optional<Integer> getSomethingOptinal() {
    return Optional.empty();
}

我们直接调用这个方法

getSomethingOptinal().ifPresent(System.out::print);

正常执行,没有异常。 所以现在当我们一个方法返回List集合时,应该总是返回一个空的List,而不是Null; 当一个返回T类型的方法有可能为Null 时,应该该用Optional<T>。

© 著作权归作者所有

MiaoXG
粉丝 2
博文 19
码字总数 17098
作品 0
朝阳
程序员
私信 提问
强大的Stream API(一) Stream的创建

Java 8 给我们提供了Stream API,为什么叫强大的Stream API?接下来我将一一讲解一些,从中你就会慢慢体会到它的强大。 关于Stream API的使用,我会分好几篇,是一个循序渐进的过程,这里着重...

guofei_wu
2017/12/20
0
0
Lambda表达式实战视频教程

视频教程地址: http://edu.51cto.com/course/10768.html 1:Lambda表达式及函数式接口介绍 2:Lambda表达式详解 3:方法的引用(一) 4:方法的引用(二) 5:Stream API(一) 6:Stream ...

刘宗泽
2017/08/13
0
0
Java 8 vs Scala — Part II Streams API

这是本文的第 2 部分。第 1 部分在这里。 Stream 与 Collection 的比较 这是我按自己的意思给的一个十分简要的说明:collection 是一个有限的数据集,而 stream 是数据的一个序列,可以是有限...

oschina
2015/11/18
4K
16
Java 8 vs. Scala(二):Stream vs. Collection

【编者按】在之前文章中,我们介绍了 Java 8和Scala的Lambda表达式对比。在本文,将进行 Hussachai Puripunpinyo Java 和 Scala 对比三部曲的第二部分,主要关注 Stream 和 Collection,本文...

OneAPM蓝海讯通
2015/11/30
30
0
Java 8 Stream的性能到底如何?

已经对Stream API的用法鼓吹够多了,用起简洁直观,但性能到底怎么样呢?会不会有很高的性能损失?本节我们对Stream API的性能一探究竟。 为保证测试结果真实可信,我们将JVM运行在模式下,测...

HollisChuang's Blog
03/16
0
0

没有更多内容

加载失败,请刷新页面

加载更多

UAVStack功能上新:新增JVM监控分析工具

UAVStack推出的JVM监控分析工具提供基于页面的展现方式,以图形化的方式展示采集到的监控数据;同时提供JVM基本参数获取、内存dump、线程分析、内存分配采样和热点方法分析等功能。 引言 作为...

宜信技术学院
20分钟前
4
0
MySQL的5种时间类型的比较

日期时间类型 占用空间 日期格式 最小值 最大值 零值表示 DATETIME 8 bytes YYYY-MM-DD HH:MM:SS 1000-01-01 00:00:00 9999-12-31 23:59:59 0000-00-00 00:00:00 TIMESTAMP 4 bytes YYYY-MM......

物种起源-达尔文
27分钟前
6
0
云服务OpenAPI的7大挑战,架构师如何应对?

阿里妹导读:API 是模块或者子系统之间交互的接口定义。好的系统架构离不开好的 API 设计,而一个设计不够完善的 API 则注定会导致系统的后续发展和维护非常困难。比较好的API设计样板可以参...

阿里云官方博客
30分钟前
5
0
Rancher + VMware PKS实现全球数百站点的边缘K8S集群管理

Sovereign Systems是一家成立于2007年的技术咨询公司,帮助客户将传统数据中心技术和应用程序转换为更高效的、基于云的技术平台,以更好地应对业务挑战。曾连续3年提名CRN,并且在2012年到2...

RancherLabs
35分钟前
5
0
6、根据坐标,判断该坐标是否在地图区域范围内

最近在写配送区域相关的代码,具体需求如下: 根据腾讯地图划分配送区域,总站下边设多个配送分站,然后将订单中的收货地址将其分配给不同的配送分站。 1、地图区域划分(腾讯地图) 1.1、H...

有一个小阿飞
36分钟前
4
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部