在想很好了解 Stream 之前,很有必要简单的了解下函数式变成以及Lambda的概念,可以阅读另外一篇
大家回忆下日常学习工作中使用的最多的 Java API 是什么?相信很多人的答案和我一样都是集合。我们选择适合的集合数据结构存储数据,而我们之于集合最多的操作就是遍历,实现查询,统计,过滤,合并等业务。
外部迭代:通过 for循环,Iterator迭代器遍历集合,手动的拿到集合中每个元素进行相应处理
内部迭代:只提供对集合中元素的处理逻辑,遍历过程交给库类,Java5提供了foreach,Java8提供了Stream
本文要介绍的Stream属于内部迭代,之前我们已经有了foreach减少了我们的代码量,为什么我们还需要Stream呢?
上文我们大概知道了Stream主要服务与集合,流的过程可以总结为三步:
第一步流加载集合数据,库类完成我们不需要关心,要想使用好Stream有必要从不同维度了解主要的操作
下图给出了Stream给出的一些常用api的基本信息
为了避免自动装箱拆箱消耗性能,Stream为我们提供了IntStream、DoubleStream和LongStream,分别将流中的元素特化为int、long和double,这些特殊的流中提供了range,sum,max等数字类型常用的api
当我们面对计算是否密集的应用开发时,为了充分利用硬件资源,可以简单的通过改变parallel方法将流变成并行执行,但是在使用时有如下注意事项。
相信大家最关心的还是实际开发中能帮助我们解决哪些问题,通过一些简单案例熟悉各种操作的用法
Stream<Object> empty = Stream.empty();
Stream<String> stringStream = Stream.of("1", "2", "3");
String[] strings = { "1", "2", "3"};
Stream<String> stream = Arrays.stream(strings);
List<String> strings1 = Arrays.asList("1", "2", "3");
Stream<String> stream1 = strings1.stream();
Stream<String> lines = Files.lines(Paths.get("/c/mnt/"));
// 无限流,流从0开始,下面的每个元素依次加2
Stream<Integer> iterate = Stream.iterate(0, num -> num + 2);
// 无限流,流中每个元素都是 0~1 随机数
Stream<Double> generate = Stream.generate(Math::random);
// 生成0到10的int流
IntStream intStream = IntStream.rangeClosed(0, 10);
// 生成0到9的int流
IntStream intStream1 = IntStream.range(0, 10);
// 生成有字符串a和数字1的异构的流
Stream.builder().add("a").add(1).build().forEach(System.out::print);
Stream.concat(
Stream.of("1", 22, "333"),
Stream.of("1", 22, 333)
).forEach(System.out::print);
在介绍中间操作时,为了方便学习演示使用到了终端操作中的foreach方法,作用就和我们写的foreach循环类似,遍历执行,不返回值。
// 过滤所有空字符串,注意是返回 true的留下
Stream.of("1", null, "2", "", "3")
.filter(StringUtils::isNotEmpty)
.forEach(System.out::print);
// 去掉重复的2
Stream.of("1", "2", "2", "2", "3")
.distinct()
.forEach(System.out::print);
// 跳过前两个元素
Stream.of("1", "2", "3", "4", "5")
.skip(2)
.forEach(System.out::print);
// 与skip相反,只留下前2个
Stream.of("1", "2", "3", "4", "5")
.limit(2)
.forEach(System.out::print);
@Data
@AllArgsConstructor
public class Person {
private String name;
}
// 将流中每个字符串转为Person实例
Stream.of("1", "2", "3")
.map(Person::new)
.forEach(System.out::print);
// 将流每个字符串变成其长度,为了避免自动拆箱,使用mapToInt转为IntStream
Stream.of("1", "2", "3")
.mapToInt(String::length)
.forEach(System.out::print);
用法和map类似,当我们需要把流中的每个元素全映射另外一个流,也就是数据在流中流里面,这时候操作就不方便,借助flatMap,我们可以把所有第二层流中的元素合并到最外层流
// 每个字符串按照逗号分隔合并成一个流
Stream.of("1,2,3", "4,5,6", "7,8,9")
.flatMap(a -> Stream.of(a.split(",")))
.forEach(System.out::print);
Stream.of("4", "3", "5")
.sorted()
.forEach(System.out::print);
Stream.of("4", "3", "5")
.sorted(Comparator.naturalOrder())
.forEach(System.out::print);
// 串行流转并行流
Stream.of("1", "2", "3").parallel();
// 并行流转串行流
Arrays.asList("1", "2", "3").parallelStream().sequential();
// 在并行流中加上unordered,使得流变成无序,提供并行效率,此时limit相当于随机取2个元素
Arrays.asList("1", "2", "3", "4", "5")
.parallelStream()
.unordered()
.limit(2).forEach(System.out::print);
用于debug调试代码,在每次执行操作前,看一眼元素
// 每个元素会打印两遍
Stream.of("1", "2", "3").peek(System.out::print).forEach(System.out::print);
Stream.of("1", "2", "3").forEach(System.out::print);
// 并行流中使用用于保持有序
Arrays.asList("1", "2", "3").parallelStream().forEachOrdered(System.out::print);
// 总个数,返回true|false
Stream.of("1", "2", "3").count();
// 最大元素,返回Optional
Stream.of("1", "2", "3").max(Comparator.naturalOrder());
// 最小元素,返回Optional
Stream.of("1", "2", "3").min(Comparator.naturalOrder());
以 IntStream 举例
// 累加求和
Stream.of("1", "2", "3").mapToInt(Integer::valueOf).sum();
// 求平均数
Stream.of("1", "2", "3").mapToInt(Integer::valueOf).average();
// 总和,最大值,最小值,平均数,总个数,应有尽有
Stream.of("1", "2", "3").mapToInt(Integer::valueOf).summaryStatistics();
返回true|false
// 是否有长度大于 2 的字符串
Stream.of("1", "22", "333").anyMatch(s -> s.length() > 2);
// 是否一个长度大于 2 的字符串也没有
Stream.of("1", "22", "333").noneMatch(s -> s.length() > 2);
// 是否字符串长度全大于 2
Stream.of("1", "22", "333").allMatch(s -> s.length() > 2);
由于是短路操作,所以只有在串行流中findAny和findFirst才区别明显
// 找到流中任意一个元素,普通流一般也返回第一个元素,并行流中返回任意元素
Stream.of("1", "22", "333").findAny();
// 找到流中第一个元素,普通流和并行流都一样
Stream.of("1", "22", "333").findFirst();
把所有处理结果汇总,Collectors收集器里提供了很多常用的汇总操作
// 将结果汇总成一个list
Stream.of("1", "22", "333").collect(Collectors.toList());
谷歌著名的map-reduce理想,用于最后汇总结果
// 0作为起始的pre,流的结果等于pre乘以自身再加一,直到curr到达最后一个元素
// (1) pre = 0 curr = 1 计算 pre = 0 * 1 + 1 = 1
// (2) pre = 1 curr = 2 计算 pre = 1 * 2 + 1 = 3
// (3) pre = 3 curr = 3 计算 pre = 3 * 3 + 1 = 10
// (4) pre = 10 curr = null 返回结果 10
IntStream.of(1, 2, 3).reduce(0, (pre, curr) -> pre * curr + 1);
// 当然如果不设置初始值,流中第一个元素就是pre
IntStream.of(1, 2, 3).reduce((pre, curr) -> pre * curr + 1);
Stream.of("1", "22", "333").toArray();
Stream.of("1", "2", "3").iterator();
Stream.of("1", "2", "3").spliterator();
Stream.of("1", "2", "3").isParallel();
在介绍 collect 操作中,我们用了 Collectors 中提供的 toList 方法将结果汇总成List,collect 是很常用的操作Collectors中有很多有用的方法值得熟悉一下,其实很多终端方法都是 collect 的快捷写法,如果都不能满足需求我们还可以自己实现一个
// 转list
List<String> collect = Stream.of("1", "2", "3").collect(Collectors.toList());
// 转set
Set<String> collect = Stream.of("1", "2", "3").collect(Collectors.toSet());
// 转map,key为字符串长度,value为字符串本身
Map<Integer, String> collect = Stream.of("1", "2", "3")
.collect(Collectors.toMap(String::length, Function.identity()));
// 转并发版map,key为字符串长度,value为字符串本身
Map<Integer, String> collect = Stream.of("1", "2", "3")
.collect(Collectors.toConcurrentMap(String::length, Function.identity()));
// 转指定类型集合
ArrayList<String> collect = Stream.of("1", "2", "3")
.collect(Collectors.toCollection(ArrayList::new));
// 拼接成一个字符串
String collect = Stream.of("1", "2", "3").collect(Collectors.joining());
// 拼接成一个字符串,逗号分隔
String collect = Stream.of("1", "2", "3").collect(Collectors.joining(","));
都有对应简化版,一些更加灵活多变的操作可以用Collectors
// 和终端操作中的 max 等价
Stream.of("1", "2", "3").collect(Collectors.maxBy(Comparator.naturalOrder()));
// 和终端操作中的 min 等价
Stream.of("1", "2", "3").collect(Collectors.minBy(Comparator.naturalOrder()));
// 和终端操作中的 count 等价
Stream.of("1", "2", "3").collect(Collectors.counting(Integer::valueOf));
// 和数值流终端操作中的 sum 等价
Stream.of("1", "2", "3").collect(Collectors.summingInt(Integer::valueOf));
// 和数值流终端操作中的 average 等价
Stream.of("1", "2", "3").collect(Collectors.averagingInt(Integer::valueOf));
// 和数值流终端操作中的 summaryStatistics 等价
Stream.of("1", "2", "3").collect(Collectors.summarizingInt(Integer::valueOf));
可以和其他收集器方法任意组合
// 以字符串长度分成3组,map的key为长度,value为对应长度字符串list
Map<Integer, List<String>> collect = Stream.of("1", "22", "33", "4", "555")
.collect(Collectors.groupingBy(String::length));
// 以字符串长度分成3组,map的key为长度,value为对应的元素个数
Map<Integer, Long> collect = Stream.of("1", "22", "33", "4", "555")
.collect(Collectors.groupingBy(String::length, Collectors.counting()));
// 这个例子没有实际意义,展示我们可以进行二级分组,长度分组完毕,再组内以hash值分组
Map<Integer, Map<Integer, List<String>>> collect = Stream.of("1", "22", "33", "4", "555")
.collect(Collectors.groupingBy(
String::length,
Collectors.groupingBy(String::hashCode)
));
分区时特殊的分组,使用方法类似,特殊是通过谓词表达式只能分成两组,true是一组,false是一组
// 以长度大于2为标准分区
Map<Boolean, List<String>> collect = Stream.of("1", "22", "33", "4", "555")
.collect(Collectors.partitioningBy(s -> s.length() > 2));
// 和终端操作 reduce 等价
Stream.of("1", "22", "33", "4", "555")
.collect(Collectors.reducing(0, Integer::valueOf, Integer::sum))
// 把流汇总成list,然后再求出其容量
Integer collect = Stream.of("1", "22", "33", "4", "555")
.collect(Collectors.collectingAndThen(Collectors.toList(), List::size));
有些时候默认的实现有缺陷,或者追求更高的性能我们需要自己实现收集器。只要实现 Collector<T, A , R>接口 中的方法我们就可以获得自己的收集器,其中 T 是元素泛型,A是累加器结果,R是最终返回结果,所有首先我们来看下要实现哪些方法
public static final Collector<String, List<String>, List<String>> myToList = Collector.of(
// supplier: 创建 A(ArrayList)
ArrayList::new,
// accumulator:把每个元素放入 A 中
(list, el) -> list.add(el),
// combiner:如果并行拆分成多个流,直接 addAll 合并
// 如果不想支持并行可以写个空,或抛UnsupportedOperationException异常
(listA, listB) -> {
listA.addAll(listB);
return listA;
},
// finisher:不做任何事情,直接返回 A
Function.identity(),
// characteristics...:表示 A R 类型相同, 且支持并行流
Collector.Characteristics.IDENTITY_FINISH,
Collector.Characteristics.CONCURRENT
);
// 自定义收集器转成list
List<String> collect = Stream.of("1", "22", "33", "4", "555").collect(myToList);
原文:https://www.cnblogs.com/freshchen/p/12313999.html