Stream流的使用
创建流
创建流的方式很多,从jdk8
起,很多类中添加了一些方法来创建相应的流,比如:BufferedReader
类的lines()
方法;Pattern
类的splitAsStream
方法。但是开发中使用到Stream
基本上都是对集合的操作,了解如下几种创建方式即可:
// 集合与数组
List<String> list = new ArrayList<>();
String[] arr = new String[]{};
Stream<String> listStream = list.stream();
Stream<String> arrayStream = Arrays.stream(arr);
// 静态方法创建指定元素的流
Stream<String> programStream = Stream.of("java", "c++", "c");
// 将多个集合合并为一个流
List<String> sub1 = new ArrayList<>();
List<String> sub2 = new ArrayList<>();
Stream<String> concatStream = Stream.concat(sub1.stream(), sub2.stream());
// 提供一个供给型函数式接口, 源源不断创造数据, 创建无限流
// 如下创建一个无限的整数流, 源源不断打印10以内的随机数
Stream<Integer> generateStream = Stream.generate(() -> RandomUtil.randomInt(10));
generateStream.forEach(System.out::println);
// limit可控制返回流中的前n个元素, 此处仅取前2个随机数打印
Stream<Integer> limitStream = generateStream.limit(2);
limitStream.forEach(System.out::println);
中间操作
筛选
filter:入参为断言型接口(Predicate<T>
),即用于筛选出断言函数返回true
的元素
limit:截断流,获取前n个元素的流
skip:跳过n个元素
distinct:通过流中元素的equals
方法比较,去除重复的元素
这四个筛选类方法较为简单,示例如下:
List<Map<String, Object>> source = ImmutableList.of(
ImmutableMap.of("id", 1),
ImmutableMap.of("id", 2),
ImmutableMap.of("id", 2),
ImmutableMap.of("id", 3),
ImmutableMap.of("id", 4)
);
Stream<Map<String, Object>> target = source.stream()
// 先筛选出id > 1的数据
.filter(item -> Convert.toInt(item.get("id")) > 1)
.distinct() // 去重
.skip(1) // 跳过一个元素
.limit(1); // 只返回第一个元素
target.forEach(System.out::println); // 输出: {id=3}
映射
map
map
方法入参为函数式接口(Function<T, R>
),即将流中的每个元素映射为另一个元素;
如下,取出source
中的每个map
元素,取id
和age
属性拼成一句话当做新的元素
List<Map<String, Object>> source = ImmutableList.of(
ImmutableMap.of("id", 1, "age", 10),
ImmutableMap.of("id", 2, "age", 12),
ImmutableMap.of("id", 3, "age", 16)
);
Stream<String> target = source.stream()
.map(item -> item.get("id").toString() + "号嘉宾, 年龄: " + item.get("age"));
target.forEach(System.out::println);
输出:
1号嘉宾, 年龄: 10
2号嘉宾, 年龄: 12
3号嘉宾, 年龄: 16
flatMap
map
操作是将单个对象转换为另外一个对象,而flatMap
操作则是将单个对象转换为多个对象,这多个对象以流的形式返回,最后将所有流合并为一个流返回;
List<List<String>> source = ImmutableList.of(
ImmutableList.of("A_B"),
ImmutableList.of("C_D")
);
Stream<String> target = source.stream().flatMap(item -> {
String data = item.get(0);
// 将数据映射为一个数组, 即:一对多
String[] spilt = data.split("_");
// 由泛型可知, 需要返回流的形式
return Arrays.stream(spilt);
});
target.forEach(System.out::println); // 依次打印A、B、C、D
排序
sorted
对流中的元素进行排序,默认会根据元素的自然顺序排序,也可传入Comparator
接口实现指定排序逻辑
List<Integer> source = ImmutableList.of(
10, 1, 5, 3
);
source.stream()
.sorted()
.forEach(System.out::println); // 打印: 1、3、5、10
source.stream()
.sorted(((o1, o2) -> o2 - o1))
.forEach(System.out::println); // 打印: 10、5、3、1
消费
peek
该方法主要目的是用来调试,接收一个消费者函数,方法注释中用例如下:
Stream.of("one", "two", "three", "four")
.filter(e -> e.length() > 3)
.peek(e -> System.out.println("Filtered value: " + e))
.map(String::toUpperCase)
.peek(e -> System.out.println("Mapped value: " + e))
.collect(Collectors.toList());
实际使用很少,并不需要这么来调试问题
终结操作
查找与匹配
allMatch:检查是否匹配流中所有元素
anyMatch:检查是否至少匹配流中的一个元素
noneMatch:检查是否没有匹配的元素
findFirst:返回第一个元素
findAny:返回任意一个元素
max/min/count:返回流中最大/最小/总数
List<Integer> source = ImmutableList.of(
10, 1, 5, 3
);
// 元素是否都大于0
System.out.println(source.stream().allMatch(s -> s > 0));
// 是否存在元素大于9
System.out.println(source.stream().anyMatch(s -> s > 9));
// 是否不存在元素大于10
System.out.println(source.stream().noneMatch(s -> s > 10));
// 返回第一个元素, 若不存在则返回0
System.out.println(source.stream().findFirst().orElse(0));
// 任意返回一个元素, 若不存在则返回0
System.out.println(source.stream().findAny().orElse(0));
reduce
该方法可用于聚合求值,有三个重载方法;
reduce(BinaryOperator<T> accumulator)
reduce(T identity, BinaryOperator<T> accumulator)
reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner)
先看第一种重载形式:
当入参仅为函数式接口BinaryOperator<T>
时,定义了如何将流中的元素聚合在一起,即将流中所有元素组合成一个元素;注意到BinaryOperator<T>
接口继承自BiFunction<T,T,T>
,从泛型可知接口入参和出参都是同类型的
// 查询出在不同年龄拥有的财产
List<Map<String, Object>> source = ImmutableList.of(
ImmutableMap.of("age", 10, "money", "21.2"),
ImmutableMap.of("age", 20, "money", "422.14"),
ImmutableMap.of("age", 30, "money", "3312.16")
);
// 计算年龄总和和财产总和
Map<String, Object> res = source.stream().reduce((firstMap, secondMap) -> {
Map<String, Object> tempRes = new HashMap<>();
tempRes.put("age", Integer.parseInt(firstMap.get("age").toString())
+ Integer.parseInt(secondMap.get("age").toString()));
tempRes.put("money", Double.parseDouble(firstMap.get("money").toString())
+ Double.parseDouble(secondMap.get("money").toString()));
// 流中的元素是map, 最终也只能聚合为一个map
return tempRes;
}).orElseGet(HashMap::new);
System.out.println(JSONUtil.toJsonPrettyStr(res));
// 输出
{
"money": 3755.5,
"age": 60
}
BinaryOperator<T>
函数的两个入参如何理解呢?如下:
容易看出其含义就是将流中元素整合在一起,但是这种方法的初始值就是流中的第一个元素,能否自定义聚合的初始值呢?
这就是第二种重载形式了,显然,第一个参数就是指定聚合的初始值;
紧接着上个例子,假设人一出生就拥有100块钱(100块都不给我?),如下:
List<Map<String, Object>> source = ImmutableList.of(
ImmutableMap.of("age", 10, "money", "21.2"),
ImmutableMap.of("age", 20, "money", "422.14"),
ImmutableMap.of("age", 30, "money", "3312.16")
);
// 计算年龄总和和财产总和
Map<String, Object> res = source.stream().reduce(ImmutableMap.of("age", 0, "money", "100"),
(firstMap, secondMap) -> {
Map<String, Object> tempRes = new HashMap<>();
tempRes.put("age", Integer.parseInt(firstMap.get("age").toString())
+ Integer.parseInt(secondMap.get("age").toString()));
tempRes.put("money", Double.parseDouble(firstMap.get("money").toString())
+ Double.parseDouble(secondMap.get("money").toString()));
// 流中的元素是map, 最终也只能聚合为一个map
return tempRes;
});
System.out.println(JSONUtil.toJsonPrettyStr(res));
// 输出
{
"money": 3855.5,
"age": 60
}
注意到第一种形式没有指定初始值,所以会返回一个Optional
值,而第二种重载形式既定了初始值,也就是不会为空,所以返回值不需要Optional
类包装了。
如上我们既可以定义初始值,又可以定义聚合的方式了,还缺什么呢?
有一点小缺就是上面两种返回的结果集的类型,跟原数据流中的类型是一样的,无法自定义返回的类型,这点从BinaryOperator
参数的泛型可以看出。
所以第三种重载形式出场了,既可自定义返回的数据类型,又支持自定义并行流场景下的多个线程结果集的组合形式;
如下返回Pair
类型:
List<Map<String, Object>> source = ImmutableList.of(
ImmutableMap.of("age", 10, "money", "21.2"),
ImmutableMap.of("age", 20, "money", "422.14"),
ImmutableMap.of("age", 30, "money", "3312.16")
);
// 计算年龄总和和财产总和
Pair<Integer, Double> reduce = source.stream().reduce(Pair.of(0, 100d),
(firstPair, secondMap) -> {
int left = firstPair.getLeft()
+ Integer.parseInt(secondMap.get("age").toString());
double right = firstPair.getRight() +
+Double.parseDouble(secondMap.get("money").toString());
return Pair.of(left, right);
}, (o, n) -> o);
System.out.println(JSONUtil.toJsonPrettyStr(reduce));
其中(o, n) -> o
是随便写的,因为在顺序流中这段函数不会执行,也就是无效的,只关注前两个参数:如何定义返回的数据和类型、如何定义聚合的逻辑。
坑点:
- 如果创建并行流,且使用前两种重载方法,最终得到的结果可能会和上面举例的有些差别,因为从上面的原理来看,reduce中的每一次运算,下一步的结果总是依赖于上一步的执行结果的,像这样肯定无法并行执行,所以并行流场景下,会有一些不同的细节问题
- 当创建并行流,且使用了第三种重载方法,得到的结果可能和预期的也不一样,这需要了解其内部到底是如何聚合多个线程的结果集的
目前开发中没有详细使用并行流的经验,有待研究
collect
collect是个非常有用的操作,可以将流中的元素收集成为另一种形式便于我们使用;该方法需要传入Collector
类型,但是手动实现此类型还比较麻烦,通常用Collectors
工具类来构建我们想要返回的形式:
构建方法 | 说明 |
---|---|
Collectors.toList() | 将流收集为List形式 |
Collectors.toSet() | 将流收集为Set形式 |
Collectors.toCollection() | 收集为指定的集合形式,如LinkedList...等 |
Collectors.toMap() | 收集为Map |
Collectors.collectingAndThen() | 允许对生成的集合再做一次操作 |
Collectors.joining() | 用来连接流中的元素,比如用逗号分隔元素连接起来 |
Collectors.counting() | 统计元素个数 |
Collectors.summarizingDouble/Long/Int() | 为流中元素生成统计信息,返回的是一个统计类 |
Collectors.averagingDouble/Long/Int() | 对流中元素做平均 |
Collectors.maxBy()/minBy() | 根据指定的Comparator,返回流中最大/最小值 |
Collectors.groupingBy() | 根据某些属性分组 |
Collectors.partitioningBy() | 根据指定条件 |
如下举例一些使用:
toList/toSet/toCollection
将元素收集为集合形式
List<Map<String, Object>> source = ImmutableList.of(
ImmutableMap.of("name", "小明", "grade", "a1", "sex", "1"),
ImmutableMap.of("name", "小红", "grade", "a2", "sex", "2"),
ImmutableMap.of("name", "小白", "grade", "a1", "sex", "1"),
ImmutableMap.of("name", "小黑", "grade", "a3", "sex", "1"),
ImmutableMap.of("name", "小黄", "grade", "a4", "sex", "2")
);
// toSet类似, toCollection指定要返回的集合即可
List<String> toList = source.stream()
.map(map -> map.get("name").toString())
.collect(Collectors.toList());
System.out.println(toList); // [小明, 小红, 小白, 小黑, 小黄]
collectingAndThen
收集为集合之后再额外做一次操作
List<String> andThen = source.stream()
.map(map -> map.get("name").toString())
.collect(Collectors.collectingAndThen(Collectors.toList(), x -> {
// 将集合翻转
Collections.reverse(x);
return x;
}));
// 由于上述方法在toList之后, 增加了一个函数操作使集合翻转, 所以结果跟上个示例是反的
System.out.println(andThen); // [小黄, 小黑, 小白, 小红, 小明]
toMap
收集为map,使用此方法务必要传入第三个参数,用于表明当key冲突时如何处理
Map<String, String> toMap = source.stream()
.collect(Collectors.toMap(
// 取班级字段为key
item -> item.get("grade").toString(),
// 取名字为value
item -> item.get("name").toString(),
// 此函数用于决定当key重复时, 新value和旧value如何取舍
(oldV, newV) -> oldV + "_" + newV));
System.out.println(toMap); // {a1=小明_小白, a2=小红, a3=小黑, a4=小黄}
summarizingDouble/Long/Int
可用于对基本数据类型的数据作一些统计用,使用很少
// 对集合中sex字段统计一些信息
IntSummaryStatistics statistics = source.stream()
.collect(Collectors.summarizingInt(map -> Integer.parseInt(map.get("sex").toString())));
System.out.println("平均值:" + statistics.getAverage()); // 1.4
System.out.println("最大值:" + statistics.getMax()); // 2
System.out.println("最小值:" + statistics.getMin()); // 1
System.out.println("元素数量:" + statistics.getCount()); // 5
System.out.println("总和:" + statistics.getSum()); // 7
groupingBy
分组是用得比较多的一种方法,其有三种重载形式:
groupingBy(Function<? super T, ? extends K> classifier)
groupingBy(Function<? super T, ? extends K> classifier, Collector<? super T, A, D> downstream)
groupingBy(Function<? super T, ? extends K> classifier, Supplier<M> mapFactory, Collector<? super T, A, D> downstream)
点进源码中很容易发现,其实前两种都是第三种的特殊形式;
从第一种重载形式看起:
Function classifier:分类器,是个Function
类型接口,此参数用于指定根据什么值来分组,Function
接口返回的值就是最终返回的Map
中的key
List<Map<String, Object>> source = ImmutableList.of(
ImmutableMap.of("name", "小明", "grade", "a1", "sex", "1"),
ImmutableMap.of("name", "小红", "grade", "a2", "sex", "2"),
ImmutableMap.of("name", "小白", "grade", "a1", "sex", "1"),
ImmutableMap.of("name", "小黑", "grade", "a3", "sex", "1"),
ImmutableMap.of("name", "小黄", "grade", "a4", "sex", "2")
);
Map<String, List<Map<String, Object>>> grade = source.stream()
.collect(Collectors.groupingBy(item -> item.get("grade") + "-" + item.get("sex")));
System.out.println(JSONUtil.toJsonPrettyStr(grade));
根据班级+性别组合成的字段分组,结果如下(省略了一部分):
{
"a4-2": [
{
"sex": "2",
"grade": "a4",
"name": "小黄"
}
],
"a1-1": [
{
"sex": "1",
"grade": "a1",
"name": "小明"
},
{
"sex": "1",
"grade": "a1",
"name": "小白"
}
],
...
}
可以看到key
是Function
接口中选择的grade
+sex
字段,value
是原数据集中元素的集合;
既然key
的形式可以控制,value
的形式如何控制呢?这就需要第二种重载形式了
Collector downstream:该参数就是用于控制分组之后想返回什么形式的值,Collector
类型,所以可以用Collectors
工具类的方法来控制结果集形式;根据此特性,可以实现多级分组;
点进第一种重载方法源码中可以看到,不传第二个参数时,默认取的是Collectors.toList()
,所以最后返回的Map
中的value
是集合形式,可以指定返回其他的形式。
比如上个例子,分组之后返回每组有多少个元素就行,不需要具体元素的集合
List<Map<String, Object>> source = ImmutableList.of(
ImmutableMap.of("name", "小明", "grade", "a1", "sex", "1"),
ImmutableMap.of("name", "小红", "grade", "a2", "sex", "2"),
ImmutableMap.of("name", "小白", "grade", "a1", "sex", "1"),
ImmutableMap.of("name", "小黑", "grade", "a3", "sex", "1"),
ImmutableMap.of("name", "小黄", "grade", "a4", "sex", "2")
);
Map<String, Long> collect = source.stream()
.collect(Collectors.groupingBy(item -> item.get("grade") + "-" + item.get("sex"),
Collectors.counting()));
System.out.println(JSONUtil.toJsonPrettyStr(collect));
结果就是返回的Map
中的value
是元素个数了:
{
"a4-2": 1,
"a1-1": 2,
"a3-1": 1,
"a2-2": 1
}
再看最后返回的结果集Map
,其中的key
和value
都可以自由控制形式了,那Map
类型具体到底是哪个实现类呢?这就是第三种重载形式的作用了
Supplier mapFactory:从参数名显而易见就是制造map
的工厂,Supplier
供给型接口,即指定返回的Map
类型就行
如果没有使用此参数指定Map
类型,那么默认返回的就是HashMap
实现,这点从方法源码中很容易看到
紧接着上个例子,返回LinkedHashMap
实现
List<Map<String, Object>> source = ImmutableList.of(
ImmutableMap.of("name", "小明", "grade", "a1", "sex", "1"),
ImmutableMap.of("name", "小红", "grade", "a2", "sex", "2"),
ImmutableMap.of("name", "小白", "grade", "a1", "sex", "1"),
ImmutableMap.of("name", "小黑", "grade", "a3", "sex", "1"),
ImmutableMap.of("name", "小黄", "grade", "a4", "sex", "2")
);
Map<String, Long> collect = source.stream()
.collect(Collectors.groupingBy(item -> item.get("grade") + "-" + item.get("sex"),
LinkedHashMap::new,
Collectors.counting()));
System.out.println(JSONUtil.toJsonPrettyStr(collect));
System.out.println(collect instanceof LinkedHashMap);
输出:
{
"a4-2": 1,
"a1-1": 2,
"a2-2": 1,
"a3-1": 1
}
true
partitioningBy
分区也是分组的一种特殊表现形式,分区操作返回的结果集Map
中的key
固定为true/false
两种,含义就是根据入参的预测型接口将数据分为两类,类比分组即可;
Stream流的使用的更多相关文章
- Atiti 重定向标准输出到字符串转接口adapter stream流体系 以及 重定向到字符串
Atiti 重定向标准输出到字符串转接口adapter stream流体系 以及 重定向到字符串 原理::syso 向ByteArrayOutputStream这个流理想write字节..然后可以使 ...
- 在stream流和byte[]中查找(搜索)指定字符串
在 stream流 和 byte[] 中查找(搜索)指定字符串 这里注重看的是两个 Search 的扩展方法,一个是 stream 类型的扩展,另一个是 byte[] 类型的扩展, 如果大家有更好的“ ...
- 这可能是史上最好的 Java8 新特性 Stream 流教程
本文翻译自 https://winterbe.com/posts/2014/07/31/java8-stream-tutorial-examples/ 作者: @Winterbe 欢迎关注个人微信公众 ...
- (六)jdk8学习心得之Stream流
六.Stream流 1. 什么是stream流 现阶段,可以把stream流看成一个高级版的Iterator.普通的Iterator只能实现遍历,遍历做什么,就需要具体些功能代码函数了.而这个stre ...
- stream流操作List工具类
工作中操作List对于程序猿来说是"基本操作",为了更加便利,对JDK8的新特性stream流进行二次封装.话不多说,直接上代码 package com.mydemo; impor ...
- java8 Stream的实现原理 (从零开始实现一个stream流)
1.Stream 流的介绍 1.1 java8 stream介绍 java8新增了stream流的特性,能够让用户以函数式的方式.更为简单的操纵集合等数据结构,并实现了用户无感知的并行计算. 1.2 ...
- NodeJS Stream流
NodeJS Stream流 流数据在网络通信中至关重要,nodeJS用Stream提供了一个抽象接口,node中有很多对象实现了这个接口,提供统一的操作体验 基本流类型 NodeJS中,Stream ...
- 关于Java8 Stream流的利与弊 Java初学者,大神勿喷
题目需求: 1:第一个队伍只要名字为3个字成员的姓名,存储到新集合 2:第一个队伍筛选之后只要前3人:存储到一个新集合 3:第2个队伍只要姓张的成员姓名:存储到一个新集合 4:第2个队伍不要前2人,存 ...
- 13函数式编程&Stream流
13.1常用的函数式接口总结 接口名称 方法名称 抽象/默认 延迟/终结 方法描述 Supplier get 抽象 终结 供给型接口,无参有返回值,主要用于 Consumer accept 抽象 ...
- Java8的Stream流(一) --- 基础用法
Java8中的Stream Stream使用一种类似用SQL语句从数据库查询数据的直观方式来提供一种对Java集合运算和表达的高阶抽象. Stream的特性及优点: 无存储. Stream不是一种数据 ...
随机推荐
- PAT (Basic Level) Practice (中文)1076 Wifi密码 (15分)
1076 Wifi密码 (15分) 下面是微博上流传的一张照片:"各位亲爱的同学们,鉴于大家有时需要使用 wifi,又怕耽误亲们的学习,现将 wifi 密码设置为下列数学题答案:A-1:B- ...
- C语言知识_1
+,-,*,/是C语言中表示四则运算的符号.:用来分割不同的语句{}用来对语句进行分组 函数代表了一组数据处理过程,由一对大括号所包含的多条语句来表示这个处理过程.每个函数有唯一的名字,main函数是 ...
- 【c++ Prime 学习笔记】第13章 拷贝控制
定义一个类时,可显式或隐式的指定在此类型对象上拷贝.移动.赋值.销毁时做什么.通过5种成员函数实现拷贝控制操作: 拷贝构造函数:用同类型的另一个对象初始化本对象时做什么(拷贝初始化) 拷贝赋值算符:将 ...
- 【做题记录】CF1444A Division
CF1444A Division 题意: 给定 \(t\) 组询问,每组给两个数 \(p_i\) 和 \(q_i\) ,找出最大的整数 \(x_i\) ,要求 \(p_i\) 可被 \(x_i\) 整 ...
- 数组中的逆序对 牛客网 剑指Offer
数组中的逆序对 牛客网 剑指Offer 题目描述 在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对.输入一个数组,求出这个数组中的逆序对的总数P.并将P对10000000 ...
- 第05课 OpenGL 3D空间
3D空间: 我们使用多边形和四边形创建3D物体,在这一课里,我们把三角形变为立体的金子塔形状,把四边形变为立方体. 在上节课的内容上作些扩展,我们现在开始生成真正的3D对象,而不是象前两节课中那样3D ...
- 第03课 OpenGL 添加颜色
添加颜色: 作为第二课的扩展,我将叫你如何使用颜色.你将理解两种着色模式,在下图中,三角形用的是光滑着色,四边形用的是平面着色 上一课中我教给您三角形和四边形的绘制方法.这一课我将教您给三角形和四边形 ...
- nohup java -jar xx.jar & ,关闭窗口后退出进程
nohup java -jar dw-report..jar > dw-report.log & 自动退出命令在后台运行 xx.jar程序 明明已经加了"&" ...
- SpringCloud微服务实战——搭建企业级开发框架(十四):集成Sentinel高可用流量管理框架【限流】
Sentinel 是面向分布式服务架构的高可用流量防护组件,主要以流量为切入点,从限流.流量整形.熔断降级.系统负载保护.热点防护等多个维度来帮助开发者保障微服务的稳定性. Sentinel 具有 ...
- xxx.app已损坏无法打开、来自身份不明的开发者解决办法
在 Mac 上安装非 App Store 软件时,可能会遇到一些这样或那样的问题,这篇文章就 Mac 从 .dmg 安装软件时可能遇到的问题提一些解决方法. 状况一:双击 .dmg 安装软件出现以下情 ...