PancrasL的博客

Java8 Stream总结和思考

2021-06-22

img

1. Java8的新特性——Stream

Java 8 API添加了一个新的抽象称为流Stream,可以以一种声明的方式处理数据,专注于对容器对象进行各种非常便利、高效的 聚合操作(aggregate operation)或者大批量数据操作。

Stream API借助于同样新出现的Lambda表达式,极大的提高编程效率和程序可读性。同时,它提供串行和并行两种模式进行汇聚操作,并发模式能够充分利用多核处理器的优势,使用fork/join并行方式来拆分任务和加速处理过程。

这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。

2. 操作符

操作符就是对数据进行的一种处理工作

Stream的操作符大体上分为:中间操作符(Intermediate)终止操作符(Terminal)短路操作符(Short-circuiting)

2.1 中间操作符

对于数据流来说,中间操作符在执行制定处理程序后,数据流依然可以传递给下一级的操作符。

中间操作符包含8种(排除了parallel,sequential,这两个操作并不涉及到对数据流的加工操作):

  • map(mapToInt,mapToLong,mapToDouble) 转换操作符,把比如A->B,这里默认提供了转int,long,double的操作符。
  • flatmap(flatmapToInt,flatmapToLong,flatmapToDouble) 拍平操作比如把 int[]{2,3,4} 拍平 变成 2,3,4 也就是从原来的一个数据变成了3个数据,这里默认提供了拍平成int,long,double的操作符。
  • distint 去重操作,对重复元素去重,底层使用了equals方法。
  • filter 过滤操作,把不想要的数据过滤。
  • peek 挑出操作,如果想对数据进行某些操作,如:读取、编辑修改等。
  • skip 跳过操作,跳过某些元素。
  • sorted(unordered) 排序操作,对元素排序,前提是实现Comparable接口,当然也可以自定义比较器。

2.2 终止操作符

数据经过中间加工操作,就轮到终止操作符上场了;终止操作符就是用来对数据进行收集或者消费的,数据到了终止操作这里就不会向下流动了,终止操作符只能使用一次。

  • collect 收集操作,将所有数据收集起来,这个操作非常重要,官方的提供的Collectors 提供了非常多收集器,可以说Stream 的核心在于Collectors。
  • count 统计操作,统计最终的数据个数。
  • min、max 最值操作,需要自定义比较器,返回数据流中最大最小的值。
  • reduce 规约操作,将整个数据流的值规约为一个值,count、min、max底层就是使用reduce。
  • forEach、forEachOrdered 遍历操作,这里就是对最终的数据进行消费了。
  • toArray 数组操作,将数据流的元素转换成数组。

2.3 短路操作符

有时候需要在遍历中途停止操作,比如查找第一个满足条件的元素或者limit操作。

  • noneMatch、allMatch、anyMatch 匹配操作,数据流中是否存在符合条件的元素 返回值为bool 值。
  • findFirst、findAny 查找操作,查找第一个、查找任何一个 返回的类型为Optional。
  • limit 限流操作,比如数据流中有10个 我只要出前3个就可以使用。

3. 代码示例

  • map:将T类型转换成R类型的
1
2
3
Stream.of("apple","banana","orange","waltermaleon","grape")
.map(e->e.length()) // 转成单词的长度 int
.forEach(e->System.out.println(e)); // 输出
  • flatMap:将拍扁的元素重新组成Stream
1
2
3
Stream.of("a-b-c-d","e-f-i-g-h")
.flatMap(e->Stream.of(e.split("-")))
.forEach(e->System.out.println(e));// 换行输出a、b、c ...
  • limit:限制元素的个数
1
2
3
Stream.of(1,2,3,4,5,6)
.limit(3) // 限制三个
.forEach(e->System.out.println(e)); // 将输出 前三个 1,2,3
  • distinct:将根据equals 方法进行判断
1
2
3
Stream.of(1,2,3,1,2,5,6,7,8,0,0,1,2,3,1)
.distinct() // 去重
.forEach(e->System.out.println(e));
  • filter:对元素进行过滤,不符合筛选条件的将无法进入流的下游
1
2
3
Stream.of(1,2,3,1,2,5,6,7,8,0,0,1,2,3,1)
.filter(e->e>=5) // 过滤小于5的
.forEach(e->System.out.println(e));
  • peek:将元素挑选出来
1
2
3
4
Stream.of(w,x,y)
.peek(e->{e.setName(e.getAge()+e.getName());}) // 重新设置名字 变成 年龄+名字
.forEach(e->System.out.println(e.toString()));

  • skip:跳过元素
1
2
3
Stream.of(1,2,3,4,5,6,7,8,9)
.skip(4) // 跳过前四个
.forEach(e->System.out.println(e)); // 输出的结果应该只有5,6,7,8,9
  • sorted:排序,底层依赖Comparable 实现,也可以提供自定义比较器
1
2
3
4
5
6
7
8
9
// 默认排序
Stream.of(2,1,3,6,4,9,6,8,0)
.sorted()
.forEach(e->System.out.println(e));

// 自定义比较器
Stream.of(w,x,y)
.sorted((e1,e2)->e1.age>e2.age?1:e1.age==e2.age?0:-1)
.forEach(e->System.out.println(e.toString()));
  • collect:收集,使用系统提供的收集器可以将最终的数据流收集到List,Set,Map等容器中。
1
2
3
Stream.of("apple", "banana", "orange", "waltermaleon", "grape")
.collect(Collectors.toSet()) //set 容器
.forEach(e -> System.out.println(e));
  • count:统计数据流中的元素个数,返回的是long 类型
1
2
long count = Stream.of("apple", "banana", "orange", "waltermaleon", "grape")
.count();
  • findFirst:获取流中的第一个元素
1
2
3
Optional<String> stringOptional = Stream.of("apple", "banana", "orange", "waltermaleon", "grape")
.findFirst();
stringOptional.ifPresent(e->System.out.println(e));
  • findAny:获取流中任意一个元素
1
2
3
4
Optional<String> stringOptional = Stream.of("apple", "banana", "orange", "waltermaleon", "grape")
.parallel()
.findAny(); //在并行流下每次返回的结果可能一样也可能不一样
stringOptional.ifPresent(e->System.out.println(e));
  • min、max:元素中最小、大的,需要传入比较器,也可能没有(流为Empty时)
1
2
3
4
Optional<Integer> integerOptional = Stream.of(0,9,8,4,5,6,-1)
.max((e1,e2)->e1.compareTo(e2));

integerOptional.ifPresent(e->System.out.println(e));

References:

[1] https://www.jianshu.com/p/11c925cdba50
[2] https://www.runoob.com/java/java8-streams.html