Java 8 Stream 总结
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
: 对象类型对应的 StreamIntStream
: 基本类型 int 对应的 StreamLongStream
: 基本类型 long 对应的 StreamDoubleStream
: 基本类型 double 对应的 Stream
如何获得 Stream
Collection to Stream
List
、Set
等 Collection
接口的实现类,可以通过 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); // 方法二
基本类型数组可以通过类似的方法转为 IntStream
、LongStream
、DoubleStream
:
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();
IntStream
、LongStream
提供了静态方法 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 asStream.filter
orStream.map
; and a terminal operation such asStream.forEach
orStream.reduce
.
Stream 操作分为 中间操作(intermediate operation)和 最终操作(terminal operation),这些操作结合起来形成 stream pipeline。stream pipeline 包含一个 Stream 源,后面跟着零到多个 intermediate operations(例如 Stream.filter
、Stream.map
),再跟上一个 terminal operation(例如 Stream.forEach
、Stream.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
: 将IntStream
、LongStream
、DoubleStream
转换为Stream<Integer>
、Stream<Long>
、Stream<Double>
peek
: 类似于forEach
,二者区别是forEach
是 terminal operation,peek
是 intermediate operationmap
、mapToInt
、mapToLong
、mapToDouble
、mapToObj
: 这些方法会传入一个函数作为参数,将 Stream 中的每个元素通过这个函数转换,转换后组成一个新的 Stream。mapToXxx
中的 Xxx 表示转换后的元素类型,也就是传入的函数返回值,例如 mapToInt 就是将原 Stream 中的每个元素转为 int 类型,最终返回一个 IntStreamflatMap
、flatMapToInt
、flatMapToLong
、flatMapToDouble
: 类似map
、mapToXxx
,不同的是flatMap
会将一个元素转为一个 Stream,其中可包含0到多个元素,最终将每个 Stream 中的所有元素组成一个新的 Stream 返回
map、flatMap 区别
map
和 flatMap
的区别就是 map
是一对一,flatMap
是一对零到多,可以用下图简单说明:
map
示例通过
mapToInt
获取一个字符串集合中每个字符串长度:Stream<String> stringStream = Stream.of("test1", "test23", "test4"); IntStream intStream = stringStream.mapToInt(String::length);
通过
String.length
函数可以将每个 String 转为一个 int,最终组成一个 IntStream。以上代码中的stringStream
和intStream
中的元素是一一对应的,每个字符串对应一个长度,两个 Stream 的元素数量是一致的。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
: 迭代StreamtoArray
: 转为数组max
: 取最大值min
: 取最小值sum
: 求和count
: Stream 中元素数量average
: 求平均数findFirst
: 返回第一个元素findAny
: 返回流中的某一个元素allMatch
: 是否所有元素都满足条件anyMatch
: 是否存在元素满足条件noneMatch
: 是否没有元素满足条件reduce
: 执行聚合操作,上面的sum
、min
、max
方法一般是基于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
实际上就是一个包含 supplier
、accumulator
、combiner
函数的类,可以实现对常用聚合算法的抽象和复用。
例如将 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 转为 ListCollectors.toSet()
: 将 Stream 转为 SetCollectors.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 总结相关推荐
- 牛逼哄洪的 Java 8 Stream,性能也牛逼么?
点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试资料 Java8的Stream API可以极大提高Java程序员的生产力 ...
- java8 group by_java8新特性Java 8 – Stream Collectors groupingBy 示例 - Java教程
在这篇教程中,将向你展示如何使用Java 8 Stream的Collectors,来对一个List进行分组,计算个数,求和以及排序. 1. Group By, Count and Sort 1.1 对 ...
- 从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 ...
- Java 8 Stream Api 中的 skip 和 limit 操作
点击上方蓝色"程序猿DD",选择"设为星标" 回复"资源"获取独家整理的学习资料! 1. 前言 Java 8 Stream API 中的sk ...
- Java 8 Stream API详解--转
原文地址:http://blog.csdn.net/chszs/article/details/47038607 Java 8 Stream API详解 一.Stream API介绍 Java 8引入 ...
- Java 8 Stream Tutorial--转
原文地址:http://winterbe.com/posts/2014/07/31/java8-stream-tutorial-examples/ This example-driven tutori ...
- Java 8 - Stream实战
文章目录 Pre 练习 基础数据 (1) 找出2011年发生的所有交易,并按交易额排序(从低到高) (2) 交易员都在哪些不同的城市工作过? (3) 查找所有来自于剑桥的交易员,并按姓名排序. (4) ...
- Java 8 - Stream流骚操作解读2_归约操作
文章目录 Pre 什么是归约操作 元素求和 reduce reduce如何运行的 最大值和最小值 Pre Java 8 - Stream流骚操作解读见到过的终端操作都是返回一个 boolean ( a ...
- Java 8 - Stream流骚操作解读
文章目录 分类 中间操作 终端操作 使用Stream流 筛选和切片 用谓词筛选 filter 筛选各异的元素 distinct 截短流 limit 跳过元素 skip 映射 对流中每一个元素应用函数 ...
- Java Streams,第 1 部分: java.util.stream 库简介
Java SE 8 中主要的新语言特性是拉姆达表达式.可以将拉姆达表达式想作一种匿名方法:像方法一样,拉姆达 表达式具有带类型的参数.主体和返回类型.但真正的亮点不是拉姆达表达式本身,而是它们所实现的 ...
最新文章
- HtmlAgilityPack 处理通配的contains
- NYOJ 990 蚂蚁感冒
- Spark任务提交后是如何完成提交过程的?源码解析!
- 领域驱动设计(DDD)的精髓
- 二十多岁不信,三十多岁却深信不疑的道理
- Graph Valid Tree
- 如何获取exe,dll中的图标以及源程序
- debian mysql中文乱码_MySQL中文乱码的解决方法汇总
- Bailian2807 两倍【序列】
- POJ 3581 Sequence(后缀数组)题解
- java中的Math类
- eclipse反编译
- 【推荐】网络安全学习路线和资料分享
- 基于J2EE的线上打印平台
- 在Azure的云服务器上搭建个人网站
- 牛客SQL3查询薪水详情和部门编号
- 某美颜app sig参数分析
- ADC采样时间、采样周期、采样频率计算方法
- 车牌识别之二:字符分割
- Hoeffding 不等式
热门文章
- 各种肤质补水六大误区 - 健康程序员,至尚生活!
- LaTeX - 毕业答辩Beamer
- Tomcat 解决“At least one JAR was scanned for TLDs yet contained no TLDs”问题
- 云堡垒机相关概念汇总说明
- python服务端设置心跳处理_Heartrate:看见 Python 程序运行的“心跳”
- “燕云十六将”之Shirley张艳(5)
- 5G工业物联网环境下多方认证性能评估
- JDK的下载,安装与配置(Win10安装方法)
- 多个图元合并其中相邻的图元
- Java、JSP宠物狗销售系统