在日常开发过程中相信大家常用Stream操作集合数据,所以本文主要讲解日常使用Stream的方法操作,便于大家也便于自己学习之用。本文不深入讲解Stream原理,有兴趣的朋友可自行查找相关资料学习。
一、什么是Stream
Stream是Java8提供了一种高效且易于使用的处理数据的方式,类似于数据库查询语句,将集合数据看做一种流,可对于流的节点进行筛选, 排序,聚合等处理。
二、Stream和Collections的区别
- 没有存储空间。流不是存储元素的数据结构。相反,它通过一系列计算操作从数据结构,数组,生成器功能或I / O通道等源中传递元素。
- 本质上是功能性的。对流的操作会产生结果,但不会修改其源。例如,对Stream 从集合中获取的a Stream进行过滤会产生一个不包含过滤元素的新元素,而不是从源集合中删除元素。
- 懒惰寻求。许多流操作(例如过滤,映射或重复删除)可以延迟实施,从而暴露出进行优化的机会。例如,“ String使用三个连续的元音查找第一个”不需要检查所有输入字符串。流操作分为中间(产生Stream)操作和最终(产生值或副作用)操作。中间操作总是很懒。
- 可能无界。尽管集合的大小是有限的,但流不需要。诸如limit(n)或 的短路操作findFirst()可以允许对无限流的计算在有限时间内完成。
- 消耗品。在流的生存期内,流的元素只能访问一次。与一样Iterator,必须生成新的流以重新访问源中的相同元素。
三、Stream的操作
Stream的操作分为中间操作和终止操作,并合并以形成流管道。
- 中间操作:中间操作返回一个新的流。他们总是 懒惰 ; 执行诸如这样的中间操作 filter()实际上并不执行任何过滤,而是创建一个新的流,该新流在遍历时将包含与给定谓词匹配的初始流的元素。在执行管道的终端操作之前,不会开始遍历管道源。
- 终端操作:(例如Stream.forEach或 IntStream.sum)可能会遍历流以产生结果或副作用。执行终端操作后,流管道被视为已消耗,无法再使用;如果需要再次遍历相同的数据源,则必须返回到数据源以获取新的流。在几乎所有情况下,终端操作人员都很渴望在返回之前完成对数据源的遍历和对管道的处理。仅终端操作iterator()而 spliterator()并非;在现有操作不足以完成任务的情况下,这些命令将作为“转义阴影线”提供,以实现任意客户端控制的管道遍历。
中间操作进一步分为无状态操作 和有状态操作。
- 无状态:不会保留先前看到的元素的状态-每个元素都可以独立于其他元素上的操作进行处理
- 有状态:在处理新元素时可以合并先前看到的元素的状态
四、常用方法
1. 生成流
- 串行流:
List<String> list=Arrays.asList("A", "B", "C");
//第一种:集合生成stream流方法
String str1=list.stream().collect(Collectors.joining(","));
//第二种:数组利用Stream.of()、Arrays.stream()生成stream流方法
String [] str=new String[]{"A", "B", "C"};
String str2=Stream.of(str).collect(Collectors.joining(","));
String str3=Arrays.stream(str).collect(Collectors.joining(","));
System.out.println(str1);
System.out.println(str2);
System.out.println(str3);
- 并行流:
//生成并行流拼接字符串
List<String> list=Arrays.asList("A", "B", "C");
String str4 = list.parallelStream().collect(Collectors.joining(","));
System.out.println(str4);
2. forEach和forEachOrdered
Stream提供了forEach和forEachOrdered方法来迭代集合中的数据,相当于for循环或者foreach循环,forEach在并行处理的时候是不按顺序执行,而forEachOrdered则严格按照顺序执行
List<String> list=Arrays.asList("A", "B", "C");
//list.forEach等价用于list.stream().forEach
list.forEach(i-> System.out.println(i));
list.stream().forEachOrdered(i-> System.out.println(i));
list.parallelStream().forEach(i-> System.out.println(i));
list.parallelStream().forEachOrdered(i-> System.out.println(i));
3. map
map方法主要处理stream中元素输出到对应的结果。
//将ABC转换成小写abc
List<String> list=Arrays.asList("A", "B", "C");
list=list.stream().map(i->i.toLowerCase()).collect(Collectors.toList());
3. flatMap
flagMap扁平化处理,将多个stream合成一个stream处理。
//将两个集合合成一个stream处理
List<String> list=Arrays.asList("A", "B", "C");
List<String> list2=Arrays.asList("D", "E", "F");
Stream.of(list,list2).flatMap(i->i.stream()).forEach(i-> System.out.println(i));
4. mapToInt、mapToDouble、mapToLong
这三个方法主要目的是把任意对象转换成基础数据类型处理,因为泛型的原因,可以有List<Integer>但是不能有List<int>,所以需要运算的时候可通过这些方法快速进行计算,除了这个三个还有flagMapToInt、flagMapToDouble、flagMapToLong。
//求和
List<Integer> numList=Arrays.asList(1, 2, 3);
System.out.println(numList.stream().mapToInt(item->item).sum());
System.out.println(numList.stream().mapToDouble(item->item).sum());
System.out.println(numList.stream().mapToLong(item->item).sum());
5.filter
filter方法用于通过设置的条件过滤出元素。
//筛选元素为A的数据
List<String> list=Arrays.asList("A", "B", "C");
list.stream().filter(i->Objects.equals(i,"A")).collect(Collectors.toList());
6.limit
limit操作也类似于SQL语句中的LIMIT关键字,limit返回包含前n个元素的流。
//只返回两个元素
List<String> list=Arrays.asList("A", "B", "C");
list.stream().limit(2).collect(Collectors.toList())
7.distinct
distinct操作类似于我们在写SQL语句时,添加的DISTINCT关键字,用于去重处理。
List<String> list=Arrays.asList("A", "B", "C", "C", "A");
list.stream().distinct().collect(Collectors.toList());
8.skip
skip操作与limit操作相反,意思是跳过前n个元素
//跳过AB,只取C
List<String> list=Arrays.asList("A", "B", "C");
list.stream().skip(2).collect(Collectors.toList());
9.min、max
min是获取最小元素、max是获取最大元素。
A a1=new A();
a1.setPrice(10);
A a2=new A();
a2.setPrice(15);
List<A> list=Arrays.asList(a1,a2);
//获取最大元素
list.stream().max(Comparator.comparing(A::getPrice));
//获取最小元素
list.stream().min(Comparator.comparing(A::getPrice));
10.reduce
将集合中的所有元素经过指定运算,折叠成一个元素输出。
第一种方式:
Optional reduce(BinaryOperator accumulator);
对Stream中的数据通过累加器accumulator迭代计算,最终得到一个Optional对象
//计算总数
Stream.of(1,2,4,5).reduce((a,b)->a+b).get()
第二种方式:
T reduce(T identity, BinaryOperator accumulator);
提供一个跟Stream中数据同类型的初始值identity,通过累加器accumulator迭代计算Stream中的数据,得到一个跟Stream中数据相同类型的最终结果
//拼接字符串
Stream.of("A", "B", "C").reduce("",String::concat);
//计算总数
Stream.of(1,2,4,5).reduce(0,(a,b)->a+b);
第三种方式:
U reduce(U identity,BiFunction<U, ? super T, U> accumulator,BinaryOperator combiner);
List<Integer> numList=Arrays.asList(1, 2, 3);
//等价于 (1+2)* (2+2)*(3+2)
list.parallelStream().reduce(2, (s1, s2) ->s1 + s2, (p, q) -> p * q);
注意:第三个参数combiner主要是使用在并行计算的场景下;如果Stream是非并行时,第三个参数实际上是不生效的。因此针对这个方法的分析需要分并行与非并行两个场景。
11.sorted(排序)
A a1=new A();
a1.setPrice(10);
A a2=new A();
a2.setPrice(15);
List<A> list=Arrays.asList(a1,a2);
//按价格升序
list.stream().sorted(Comparator.comparing(A::getPrice));
//按价格倒序
list.stream().sorted(Comparator.comparing(A::getPrice).reversed());
//如果排序字段为null,则需要Comparator.nullsFirst(null值排前面)或者Comparator.nullsLast(null值排后面)
list.stream().sorted(Comparator.comparing(A::getPrice, Comparator.nullsFirst(naturalOrder())));
list.stream().sorted(Comparator.comparing(A::getPrice, Comparator.nullsLast(naturalOrder())));
12.groupingBy(分组)
A a1=new A();
a1.setId(1L);
a1.setPrice(10);
A a2=new A();
a2.setId(3L);
a2.setPrice(15);
A a3=new A();
a3.setId(1L);
a3.setPrice(13);
List<A> as=Arrays.asList(a1,a2,a3);
//按id分组
Map<Long,List<A>> map=as.stream().collect(Collectors.groupingBy(A::getId));
13.collect
collect方法,它离不开Collectors工具类,Collectors提供了很多API方法,例如
数据收集:set、map、list
聚合归约:统计、求和、最值、平均、字符串拼接、规约
前后处理:分区、分组、自定义操作
因为实在太多方法,故此文不一一讲解
//转成list
list.stream().collect(Collectors.toList());
//转成set
list.stream().collect(Collectors.toSet());
//转成map
list.stream().collect(Collectors.toMap());
以上讲解都是日常使用频率比较多的方法,更多的方法使用方式可自行百度学习。