Stream 简介

Stream 是什么

Classes to support functional-style operations on streams of elements, such as map-reduce transformations on collections.

Stream 是 Java 8 新特性,可对 Stream 中元素进行函数式编程操作,例如 map-reduce。

先来看一段代码:

int sum = widgets.stream().filter(b -> b.getColor() == RED).mapToInt(b -> b.getWeight()).sum();

这段 Java 代码看起来是不是像通过 SQL 来操作集合:

select sum(weight) from widgets where color='RED';

Stream 类型

java.util.stream 包下提供了以下四种类型的 Stream:

  • Stream : 对象类型对应的 Stream
  • IntStream : 基本类型 int 对应的 Stream
  • LongStream : 基本类型 long 对应的 Stream
  • DoubleStream : 基本类型 double 对应的 Stream

如何获得 Stream

Collection to Stream

ListSetCollection 接口的实现类,可以通过 Collection.stream()Collection.parallelStream() 方法返回 Stream 对象:

List<String> stringList = ...;
Stream<String> stream = stringList.stream();

Array to Stream

可以通过静态方法 Arrays.stream(T[] array)Stream.of(T... values) 将数组转为 Stream:

String[] stringArray = ...;
Stream<String> stringStream1 = Arrays.stream(stringArray); //  方法一
Stream<String> stringStream2 = Stream.of(stringArray); // 方法二

基本类型数组可以通过类似的方法转为 IntStreamLongStreamDoubleStream

int[] intArray = {1, 2, 3};
IntStream intStream1 = Arrays.stream(intArray);
IntStream intStream2 = IntStream.of(intArray);

另外, Stream.of(T... values)IntStream.of(int... values) 等静态方法支持 varargs(可变长度参数),可直接创建 Stream:

IntStream intStream = IntStream.of(1, 2, 3);

Map to Stream

Map 本身不是 Collection 的实现类,没有 stream()parallelStream() 方法,可以通过 Map.entrySet()Map.keySet()Map.values() 返回一个 Collection

Map<Integer, String> map = ...;
Stream<Map.Entry<Integer, String>> stream = map.entrySet().stream();

其他

String 按字符拆分成 IntStream

String s = "Hello World";
IntStream stringStream = s.chars(); // 返回将字符串每个 char 转为 int 创建 Stream

BufferedReader 生成按行分隔的 Stream<String>

BufferedReader bufferedReader = ...;
Stream<String> lineStream = bufferedReader.lines();

IntStreamLongStream 提供了静态方法 range 生成对应的 Stream:

IntStream intStream = IntStream.range(1, 5); // 1,2,3,4 (不包含5)

Stream 的方法

intermediate operation 和 terminal operation

Stream operations are divided into intermediate and terminal operations, and are combined to form stream pipelines. A stream pipeline consists of a source (such as a Collection, an array, a generator function, or an I/O channel); followed by zero or more intermediate operations such as Stream.filter or Stream.map; and a terminal operation such as Stream.forEach or Stream.reduce.

Stream 操作分为 中间操作(intermediate operation)和 最终操作(terminal operation),这些操作结合起来形成 stream pipeline。stream pipeline 包含一个 Stream 源,后面跟着零到多个 intermediate operations(例如 Stream.filterStream.map),再跟上一个 terminal operation(例如 Stream.forEachStream.reduce)。

intermediate operation 用于对 Stream 中元素处理和转换,terminal operation 用于得到最终结果。

例如在本文开头的例子中,包含以下 intermediate operation 和 terminal operation:

int sum = widgets.stream().filter(b -> b.getColor() == RED) // intermediate operation.mapToInt(b -> b.getWeight()) // intermediate operation.sum(); // terminal operation

intermediate operation

Intermediate operations return a new stream. They are always lazy; executing an intermediate operation such as filter() does not actually perform any filtering, but instead creates a new stream that, when traversed, contains the elements of the initial stream that match the given predicate. Traversal of the pipeline source does not begin until the terminal operation of the pipeline is executed.

intermediate operation 会再次返回一个新的 Stream,所以可以支持链式调用。

intermediate operation 还有一个重要特性,延迟(lazy)性:

IntStream.of(0, 1, 2, 3).filter(i -> {System.out.println(i);return i > 1;
});

以上这段代码并不会输出:1 2 3 4,实际上这段代码运行后没有任何输出,也就是 filter 并未执行。因为 filter 是一个 intermediate operation,如果想要 filter 执行,必须加上一个 terminal operation:

IntStream.of(0, 1, 2, 3).filter(i -> {System.out.println(i);return i > 1;
}).sum();

intermediate operation 常用方法

  • filter : 按条件过滤,类似于 SQL 中的 where 语句
  • limit(long n) : 截取 Stream 的前 n 条数据,生成新的 Stream,类似于 MySQL 中的 limit n 语句
  • skip(long n) : 跳过前 n 条数据,结合 limit 使用 stream.skip(offset).limit(count),效果相当于 MySQL 中的 LIMIT offset,count 语句
  • sorted : 排序,类似于 SQL 中的 order by 语句
  • distinct : 排除 Stream 中重复的元素,通过 equals 方法来判断重复,这个和 SQL 中的 distinct 类似
  • boxed : 将 IntStreamLongStreamDoubleStream 转换为 Stream<Integer>Stream<Long>Stream<Double>
  • peek : 类似于 forEach,二者区别是 forEach 是 terminal operation,peek 是 intermediate operation
  • mapmapToIntmapToLongmapToDoublemapToObj : 这些方法会传入一个函数作为参数,将 Stream 中的每个元素通过这个函数转换,转换后组成一个新的 Stream。mapToXxx 中的 Xxx 表示转换后的元素类型,也就是传入的函数返回值,例如 mapToInt 就是将原 Stream 中的每个元素转为 int 类型,最终返回一个 IntStream
  • flatMapflatMapToIntflatMapToLongflatMapToDouble : 类似 mapmapToXxx,不同的是 flatMap 会将一个元素转为一个 Stream,其中可包含0到多个元素,最终将每个 Stream 中的所有元素组成一个新的 Stream 返回

map、flatMap 区别

mapflatMap 的区别就是 map 是一对一,flatMap 是一对零到多,可以用下图简单说明:

  1. map 示例

    通过 mapToInt 获取一个字符串集合中每个字符串长度:

    Stream<String> stringStream = Stream.of("test1", "test23", "test4");
    IntStream intStream = stringStream.mapToInt(String::length);
    

    通过 String.length 函数可以将每个 String 转为一个 int,最终组成一个 IntStream。以上代码中的 stringStreamintStream 中的元素是一一对应的,每个字符串对应一个长度,两个 Stream 的元素数量是一致的。

  2. flatMap 示例

    通过 flatMapToInt 将一个字符串集合中每个字符串按字符拆分,组成一个新的 Stream:

    Stream<String> stringStream = Stream.of("test1", "test23", "test4");
    IntStream intStream = stringStream.flatMapToInt(String::chars);
    

    每个字符串按字符拆分后可能会得到 0 到多个字符,最终得到的 intStream 元素数量和 stringStream 的元素数量可能不一致。

以下表格列出了所有map相关的方法以及转换规则:

Stream 方法 函数类型 函数参数 函数返回值 转换后
Stream<T> map Function T R Stream<R>
Stream<T> mapToInt ToIntFunction T int IntStream
Stream<T> mapToLong ToLongFunction T long LongStream
Stream<T> mapToDouble ToDoubleFunction T double DoubleStream
Stream<T> flatMap Function T Stream<R> Stream<R>
Stream<T> flatMapToInt Function T IntStream IntStream
Stream<T> flatMapToLong Function T LongStream LongStream
Stream<T> flatMapToDouble Function T DoubleStream DoubleStream
IntStream map IntUnaryOperator int int IntStream
IntStream mapToLong IntToLongFunction int long LongStream
IntStream mapToDouble IntToDoubleFunction int double DoubleStream
IntStream mapToObj IntFunction int R Stream<R>
IntStream flatMap IntFunction int IntStream IntStream
LongStream map LongUnaryOperator long long LongStream
LongStream mapToInt LongToIntFunction long int IntStream
LongStream mapToDouble LongToDoubleFunction long double DoubleStream
LongStream mapToObj LongFunction long R Stream<R>
LongStream flatMap LongFunction long LongStream LongStream
DoubleStream map DoubleUnaryOperator double double DoubleStream
DoubleStream mapToInt DoubleToIntFunction double int IntStream
DoubleStream mapToLong DoubleToLongFunction double long LongStream
DoubleStream mapToObj DoubleFunction double R Stream<R>
DoubleStream flatMap DoubleFunction double DoubleStream DoubleStream

例如对一个 Stream<Stirng> 执行 stream.mapToInt(String::length),可以理解为将一个参数为 String 返回值为 int 的函数 String::length 传入 mapToInt 方法作为参数,最终返回一个 IntStream

terminal operation

Terminal operations, such as Stream.forEach or IntStream.sum, may traverse the stream to produce a result or a side-effect. After the terminal operation is performed, the stream pipeline is considered consumed, and can no longer be used; if you need to traverse the same data source again, you must return to the data source to get a new stream.

当 terminal operation 执行过后,Stream 就不能再使用了,如果想要再使用就必须重新创建一个新的 Stream:

IntStream intStream = IntStream.of(1, 2, 3);
intStream.forEach(System.out::println); // 第一次执行 terminal operation forEach 正常
intStream.forEach(System.out::println); // 第二次执行会抛出异常 IllegalStateException: stream has already been operated upon or closed

terminal operation 常用方法

  • forEach : 迭代Stream
  • toArray : 转为数组
  • max : 取最大值
  • min : 取最小值
  • sum : 求和
  • count : Stream 中元素数量
  • average : 求平均数
  • findFirst : 返回第一个元素
  • findAny : 返回流中的某一个元素
  • allMatch : 是否所有元素都满足条件
  • anyMatch : 是否存在元素满足条件
  • noneMatch : 是否没有元素满足条件
  • reduce : 执行聚合操作,上面的 summinmax 方法一般是基于 reduce 来实现的
  • collect : 执行相对 reduce 更加复杂的聚合操作,上面的 average 方法一般是基于 collect 来实现的

reduce

先看一段使用 reduce 来实现 sum 求和的代码:

IntStream intStream = IntStream.of(1, 2, 4, 5, 8);
int sum = intStream.reduce(0, Integer::sum);

或者

IntStream intStream = IntStream.of(1, 2, 4, 5, 8);
int sum = intStream.reduce(0, (a, b) -> a + b);

上面例子中的 reduce 方法有两个参数:

  • identity : 初始值,当 Stream 中没有元素时也会作为默认值返回
  • accumulator : 一个带有两个参数和一个返回值的函数,例如上面代码中的 Integer::sum 或者 (a, b) -> a + b 求和函数

以上代码等同于:

int result = identity;
for (int element : intArray)result = Integer.sum(result, element); // 或者 result = result + element;
return result;

collect

先看一段代码,将一个 Stream<String> 中的元素拼接成一个字符串,如果用 reduce 可以这样实现:

Stream<String> stream = Stream.of("Hello", "World");
String result = stream.reduce("", String::concat); // 或者 String result = stream.reduce("", (a, b) -> a + b);

当 Stream 中有大量元素时,用字符串拼接方式性能会大打折扣,应该使用性能更高的 StringBuilder,可以通过 collect 方法来实现:

Stream<String> stream = Stream.of("Hello", "World");
StringBuilder result = stream.collect(StringBuilder::new, StringBuilder::append, StringBuilder::append);

上面例子中的 collect 方法有三个参数:

  • supplier : 传入一个函数,用于创建一个存放聚合计算结果的容器(result container),例如上面的例子中第一个传入参数 StringBuilder::new ,该函数用于创建一个新的 StringBuilder 来存放拼接字符串的结果
  • accumulator : 传入一个函数,用于将 Stream 中的一个元素合并到 result container 中,例如上面的例子中第二个传入参数 StringBuilder::append ,该函数用于将 Stream 中的字符串 append 到 StringBuilder 中
  • combiner : 传入一个函数,用于将两个 result container 合并,这个函数一般会在并行流中用到,例如上面的例子中第三个传入参数 StringBuilder::append ,该函数用于将两个 StringBuilder 合并

下面再用 collect 实现求平均数:

计算平均数需要有两个关键的数据:数量、总和,首先需要创建一个 result container 存放这两个值,并定义相关方法:

public class Averager {private int total = 0;private int count = 0;public double average() {return count > 0 ? ((double) total) / count : 0;}public void accumulate(int i) {total += i;count++;}public void combine(Averager other) {total += other.total;count += other.count;}
}

通过计算平均值:

IntStream intStream = IntStream.of(1, 2, 3, 4);
Averager averager = intStream.collect(Averager::new, Averager::accumulate, Averager::combine);
System.out.println(averager.average()); // 2.5

Collector

Stream 接口中还有一个 collect 的重载方法,仅有一个参数:collect(Collector collector)

Collector 是什么:

This class encapsulates the functions used as arguments in the collect operation that requires three arguments (supplier, accumulator, and combiner functions).

Collector 实际上就是一个包含 supplieraccumulatorcombiner 函数的类,可以实现对常用聚合算法的抽象和复用。

例如将 Stream<String> 中的元素拼接成一个字符串,用 Collector 实现:

public class JoinCollector implements Collector<String, StringBuilder, String> {@Overridepublic Supplier<StringBuilder> supplier() {return StringBuilder::new;}@Overridepublic BiConsumer<StringBuilder, String> accumulator() {return StringBuilder::append;}@Overridepublic BinaryOperator<StringBuilder> combiner() {return StringBuilder::append;}@Overridepublic Function<StringBuilder, String> finisher() {return StringBuilder::toString;}@Overridepublic Set<Characteristics> characteristics() {return Collections.emptySet();}
}

或者直接用 Collector.of() 静态方法直接创建一个 Collector 对象:

Collector<String, StringBuilder, String> joinCollector = Collector.of(StringBuilder::new,StringBuilder::append,StringBuilder::append,StringBuilder::toString);
Stream<String> stream = Stream.of("Hello", "World");
String result = stream.collect(joinCollector);

另外还有一个更简单的方式,使用 Collectors.joining()

Stream<String> stream = Stream.of("Hello", "World");
String result = stream.collect(Collectors.joining());

Collectors

java.util.stream.Collectors : 中提供了大量常用的 Collector

  • Collectors.toList() : 将 Stream 转为 List
  • Collectors.toSet() : 将 Stream 转为 Set
  • Collectors.joining() : 将 Stream 中的字符串拼接
  • Collectors.groupingBy() : 将 Stream 中的元素分组,类似于 SQL 中的 group by 语句
  • Collectors.counting() : 用于计算 Stream 中元素数量,stream.collect(Collectors.counting()) 等同于 stream.count()
  • Collectors.averagingDouble()Collectors.averagingInt()Collectors.averagingLong() : 计算平均数

上面只列出了 Collectors 中的一部分方法,还有其他常用的方法可以参考文档。

下面列出一些 Collectors 的实用示例:

  • 将 Stream 转为 List:

    Stream<String> stream = Stream.of("Hello", "World");
    List<String> list = stream.collect(Collectors.toList());
    
  • 将学生(Student)按年龄分组,返回每个年龄对应的学生列表:
    Stream<Student> stream = ...;
    Map<Integer, List<Student>> data = stream.collect(Collectors.groupingBy(Student::getAge));
    
  • 将学生(Student)按年龄分组,返回每个年龄对应的学生数量,实现和 SQL 一样的结果: select age,count(*) from student group by age
    Stream<Student> stream = ...;
    Map<Integer, Long> data = stream.collect(Collectors.groupingBy(Student::getAge, Collectors.counting()));
    
  • 计算学生(Student)年龄平均数:
    Stream<Student> stream = ...;
    Double data = stream.collect(Collectors.averagingInt(Student::getAge));
    // 或者可以 double average = stream.mapToInt(Student::getAge).average().getAsDouble();
    

参考文档

  • https://docs.oracle.com/javase/8/docs/api/java/util/stream/package-summary.html
  • https://docs.oracle.com/javase/tutorial/collections/streams/reduction.html
  • https://docs.oracle.com/javase/tutorial/collections/streams/examples/ReductionExamples.java

关注我

Java 8 Stream 总结相关推荐

  1. 牛逼哄洪的 Java 8 Stream,性能也牛逼么?

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试资料 Java8的Stream API可以极大提高Java程序员的生产力 ...

  2. java8 group by_java8新特性Java 8 – Stream Collectors groupingBy 示例 - Java教程

    在这篇教程中,将向你展示如何使用Java 8 Stream的Collectors,来对一个List进行分组,计算个数,求和以及排序. 1. Group By, Count and Sort 1.1 对 ...

  3. 从Java 8中的java.util.stream.Stream检索列表

    本文翻译自:Retrieving a List from a java.util.stream.Stream in Java 8 I was playing around with Java 8 la ...

  4. Java 8 Stream Api 中的 skip 和 limit 操作

    点击上方蓝色"程序猿DD",选择"设为星标" 回复"资源"获取独家整理的学习资料! 1. 前言 Java 8 Stream API 中的sk ...

  5. Java 8 Stream API详解--转

    原文地址:http://blog.csdn.net/chszs/article/details/47038607 Java 8 Stream API详解 一.Stream API介绍 Java 8引入 ...

  6. Java 8 Stream Tutorial--转

    原文地址:http://winterbe.com/posts/2014/07/31/java8-stream-tutorial-examples/ This example-driven tutori ...

  7. Java 8 - Stream实战

    文章目录 Pre 练习 基础数据 (1) 找出2011年发生的所有交易,并按交易额排序(从低到高) (2) 交易员都在哪些不同的城市工作过? (3) 查找所有来自于剑桥的交易员,并按姓名排序. (4) ...

  8. Java 8 - Stream流骚操作解读2_归约操作

    文章目录 Pre 什么是归约操作 元素求和 reduce reduce如何运行的 最大值和最小值 Pre Java 8 - Stream流骚操作解读见到过的终端操作都是返回一个 boolean ( a ...

  9. Java 8 - Stream流骚操作解读

    文章目录 分类 中间操作 终端操作 使用Stream流 筛选和切片 用谓词筛选 filter 筛选各异的元素 distinct 截短流 limit 跳过元素 skip 映射 对流中每一个元素应用函数 ...

  10. Java Streams,第 1 部分: java.util.stream 库简介

    Java SE 8 中主要的新语言特性是拉姆达表达式.可以将拉姆达表达式想作一种匿名方法:像方法一样,拉姆达 表达式具有带类型的参数.主体和返回类型.但真正的亮点不是拉姆达表达式本身,而是它们所实现的 ...

最新文章

  1. HtmlAgilityPack 处理通配的contains
  2. NYOJ 990 蚂蚁感冒
  3. Spark任务提交后是如何完成提交过程的?源码解析!
  4. 领域驱动设计(DDD)的精髓
  5. 二十多岁不信,三十多岁却深信不疑的道理
  6. Graph Valid Tree
  7. 如何获取exe,dll中的图标以及源程序
  8. debian mysql中文乱码_MySQL中文乱码的解决方法汇总
  9. Bailian2807 两倍【序列】
  10. POJ 3581 Sequence(后缀数组)题解
  11. java中的Math类
  12. eclipse反编译
  13. 【推荐】网络安全学习路线和资料分享
  14. 基于J2EE的线上打印平台
  15. 在Azure的云服务器上搭建个人网站
  16. 牛客SQL3查询薪水详情和部门编号
  17. 某美颜app sig参数分析
  18. ADC采样时间、采样周期、采样频率计算方法
  19. 车牌识别之二:字符分割
  20. Hoeffding 不等式

热门文章

  1. 各种肤质补水六大误区 - 健康程序员,至尚生活!
  2. LaTeX - 毕业答辩Beamer
  3. Tomcat 解决“At least one JAR was scanned for TLDs yet contained no TLDs”问题
  4. 云堡垒机相关概念汇总说明
  5. python服务端设置心跳处理_Heartrate:看见 Python 程序运行的“心跳”
  6. “燕云十六将”之Shirley张艳(5)
  7. 5G工业物联网环境下多方认证性能评估
  8. JDK的下载,安装与配置(Win10安装方法)
  9. 多个图元合并其中相邻的图元
  10. Java、JSP宠物狗销售系统