Java8-Stream流在项目中的常用方式。
1.Stream简单介绍
Stream流是Jdk1.8的高级新特性,它允许允许以声名式的方式处理数据集合,简单来说就是运用流式Api来处理数组、集合的一些数据数据处理以及格式化操作(过滤、去重、排序、分组等),它的优点是能够简化代码的编写,提高开发时操作集合的生产力,下面主要讲了一些项目中常用的流处理以及注意点。
2.StreamAPI
2.1、创建
流的创建方式有很多种,但是一般操作集合和数组的流居多,创建方式如下:
List<String> list = new ArrayList<>();
Stream<String> stream1 = list.stream(); //串行流
Stream<String> stringStream1 = list.parallelStream(); //并行流
String[] arr = new String[1024];
Stream<String> stream2 = Arrays.stream(arr);//串行流
Stream<String> parallel2 = Arrays.stream(arr).parallel(); //并行流
ps:一般在idea中我们只需要对数组、集合的引用后通过.stream就会出现流创建的提示,如下图。
2.2、中间操作
在说明中间操作Api之前,需要强调一点的就是,若只有中间操作而没有终止操作(2.3讲解)是不会触发流执行的,这种方式称为惰性求值,也就是说我们在执行流Api时结尾必须要有一个终止Api操作,否则这个流就不会执行。
中间操作的Api有很多,具体的Api以及作用见下面的表格:
API | 作用 |
---|---|
Stream |
对集合中的元素进行过滤操作。 |
Stream |
对集合元素最大数量进行限制。 |
Stream |
对集合元素进行跳过。 |
Stream |
对集合元素进行去重。 |
简单来说就是对当前处理的元素进行处理后重新放回到集合中。 | |
简单来说就是它能够将嵌套的集合进行发散处理,最后返回发散后的流。 | |
IntStream mapToInt(ToIntFunction<? super T> mapper); DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper); | 转化为对应类型的流,例如转为int流,double流等等。 |
boolean allMatch(Predicate<? super T> predicate); | 所有元素都满足条件返回true |
boolean anyMatch(Predicate<? super T> predicate); | 有一个元素满足条件就返回true |
boolean noneMatch(Predicate<? super T> predicate); | 所有元素都不满足返回true |
Optional |
返回第一个 |
long count(); | 返回流中元素的总个数 |
Optional |
传入一个比较器,返回最大元素 |
常用APi使用例举:
//构造数组
List<String> list = Arrays.asList("a", "a", "b", "c", "d", "e", "f", "", null);
//1.过滤空字符串,空值
List<String> res1 = list.stream()
.filter(StringUtils::isNoneBlank)
.collect(Collectors.toList());
log.debug("res1:{}", res1); //res1:[a, a, b, c, d, e, f]
//2.分页实现
int current = 2; //当前页码
int pageSize = 5; //一页显示的条数
List<String> collect = list.stream()
.skip((current - 1) * pageSize)
.limit(pageSize)
.collect(Collectors.toList());
log.debug("collect:{}", collect);//collect:[e, f, , null]
//3.返回元素的长度
List<Integer> collect1 = list.stream()
.map(item -> StringUtils.isBlank(item) ? 0 : item.length())
.collect(Collectors.toList());
log.debug("collect1:{}", collect1); //collect1:[1, 1, 1, 1, 1, 1, 1, 0, 0]
//构建嵌套数组
List<List<String>> lists = new ArrayList<>();
List<String> list1 = new ArrayList<>();
list1.add("a");
list1.add("b");
list1.add("c");
List<String> list2 = new ArrayList<>();
list2.add("d");
list2.add("e");
list2.add("f");
lists.add(list1);
lists.add(list2);
//4.嵌套集合发散返回String流
List<String> collect2 = lists.stream()
.flatMap(Collection::stream).collect(Collectors.toList());
log.debug("collect2:{}", collect2);//collect2:[a, b, c, d, e, f]
//5.求最大值
List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5);
int max = integers.stream().mapToInt(item -> item).summaryStatistics().getMax();
log.debug("max:{}", max);
说明:在Stream中一般参数都能使用Lambda表达式来进行参数传递(Lambda表达式类似于匿名内部类的简写,只是做了一些更为严格的要求,我们的重点不是它),在Lambda表达式中存在一些简化写法,具体为
1、对象 :: 实例方法 x->system.out.println(x) ------------>>> system.out::println
2、类 :: 静态方法 (x)-> User.fun(x) --------------------->>> User::fun
3、类 :: 实例方法 (x,y)--->x.fun(y) ------------------->>> x的类型::fun
4、创建对象 (x) ->new User(x) ----------------------------->>> User::new
一般只需要看得懂就行,如果要写这种,idea中提供了替换的快捷方式,如下图:
替换后的结果:
2.3、终止操作
终止操作主要包含reduce和collect,详细的说明如下:
reduce:主要是对元素进行一个灵活的合并处理操作,例如下面的求和操作。
Integer sum = integers.stream().
reduce(0, (item1, item2) -> {
return item1 + item2;
});
说明: 0 表示初始的默认值,item1表示前一个元素,item2表示后一个元素,return item1+item2 ; 的结果会重新放到item1中,然后继续传递下一个元素给item2。默认元素也可以省略。
collect:主要是直接对元素数据进行收集操作,提供了Collectors收集工具类,可以很方便的帮助我们对元素进行收集操作:
Collectors有很多收集操作,其实更多的是对集合进行一个数据结构的处理构造操作,返回对应数据结构的数据。下面例举几个常用的收集操作。
1.构造测试数据:
User user1 = new User();
user1.setId("1");
user1.setName("名称1");
user1.setDept("技术部");
user1.setAge(18);
User user2 = new User();
user2.setId("2");
user2.setName("名称2");
user2.setDept("技术部");
user2.setAge(19);
User user3 = new User();
user3.setId("3");
user3.setName("名称3");
user3.setDept("人事部");
user3.setAge(20);
List<User> users = Arrays.asList(user1, user2, user3);
2.Collectors.toMap(),可以快速的将集合转为Map数据结构,注意key需要唯一,如果不唯一可以定义选取方式
//key 为id ,value 为id 对应的user
Map<String, User> maps = users.stream().collect(Collectors.toMap(
item -> {
return item.getId();
}, item2 -> {
return item2;
}));
//当key相同时选取年龄大的一个
Map<String, User> map2 = users.stream().collect(Collectors.toMap(
item -> {
return item.getDept();
}, item2 -> {
return item2;
},(o1,o2)-> o1.getAge()-o2.getAge()>0 ?o1 : o2));
ps:在项目中,我们有些时候需要将数据库中的数据查到内存中做相应的业务处理操作,当涉及到多次循环检索时一般的情况是需要对数据进行循环遍历查找,这时会比较耗费性能。这时可以先将查询出来的数据转化为Map形式,然后再在map中直接获取数据从而减少多次遍历带来的性能消耗,运用Collectors.toMap()可以快速优雅的实现Map结构转化。
3.Collectors.joining(),字符串拼接
//返回所有名称并用,分隔
String names = users.stream().map(item -> item.getName()).collect(Collectors.joining(","));
log.debug("names:{}", names); //names:名称1,名称2,名称3
ps:在对pojo对象中某个字符串属性进行合并操作时可以使用这种方式。
4.Collectors.groupingBy(),分组,在程序中用的频率非常多,总结了如下一些常见的分组方式。
4.1、简单分组,直接根据某个相同属性字段分组。
//部门相同的分为一组
Map<String, List<User>> groupDept = users.stream()
.collect(Collectors.groupingBy(item -> item.getDept()));
log.debug("groupDept:{}", groupDept);
//groupDept:{
// 人事部=[User{id='3', name='名称3', dept='人事部', age=20}],
// 技术部=[User{id='1', name='名称1', dept='技术部', age=19}, User{id='2', name='名称2', dept='技术部', age=19}]
// }
ps:这种方式和Collectors.toMap()一样可以很方便的获取分组后的数据
4.2、多级分组,根据一个字段分组后继续根据另外的字段分组。
// 根据部门和年龄分组 <部门,年龄,List<User>>
Map<String, Map<Integer, List<User>>> groupDeptAge = users.stream()
.collect(Collectors.groupingBy(item -> item.getDept(),
Collectors.groupingBy(item -> item.getAge())));
log.debug("groupDeptAge:{}", groupDeptAge);
//groupDeptAge:{
// 人事部={
// 20=[User{id='3', name='名称3', dept='人事部', age=20}]
// },
// 技术部={
// 19=[User{id='1', name='名称1', dept='技术部', age=19}, User{id='2', name='名称2', dept='技术部', age=19}]
// }
// }
ps:当采用这种多级分组方式分组有一个好处,就是能够对各分组级别的分组数据进行层级获取,如上面的案例代码,可以先根据dept获取dept相同的数据,然后进行逻辑操作,在逻辑操作时又可以继续去获取某个dept组中age相同的组。
4.3、多字段合并分组,(即把多个字段合并为一个key然后分组)这种方式就不会存在map嵌套
//根据部门年龄合并分组<部门年龄,List<User>>
Map<String, List<User>> groupDeptAndAge = users.stream()
.collect(Collectors.groupingBy(item -> item.getDept() + item.getAge()));
log.debug("groupDeptAndAge:{}", groupDeptAndAge);
// groupDeptAndAge:{
// 技术部19=[User{id='1', name='名称1', dept='技术部', age=19}, User{id='2', name='名称2', dept='技术部', age=19}],
// 人事部20=[User{id='3', name='名称3', dept='人事部', age=20}]
// }
ps:这种多属性分组方式也很常用,这种分组方式更多的是进行最终分组获取,不用去根据Map层级获取,当业务没有层级获取逻辑操作,而是一步到位直接根据多字段确定组别时,可以采用这种方式,它能够使得获取更为简洁。
4.4、分组统计
//分组统计
//平均值
Map<String, Double> groupDeptSumAge = users.stream()
.collect(Collectors.groupingBy(item -> item.getDept(),
Collectors.averagingDouble(item -> item.getAge())));
log.debug("部门均龄:{}", groupDeptSumAge); //部门均龄:{人事部=20.0, 技术部=19.0}
//统计数量
Map<String, Long> groupDeptCounting = users.stream().collect(Collectors.groupingBy(item -> item.getDept(), Collectors.counting()));
log.debug("部门数量:{}", groupDeptCounting); //部门数量:{人事部=1, 技术部=2
//也可以统计其他聚合操作 ...
ps:分组统计时也用的相对较多。
在对数据库数据进行查询时,如果一次性把数据查询到内存中可能导致实体类对象创建较多,如果业务没有操作所有对象的需求,这时可以现在数据库进行一次分组统计,再在业务层面进行分组格式化处理。
5、统计操作
DoubleSummaryStatistics collect = users.stream()
.collect(Collectors.summarizingDouble(item -> item.getAge()));
log.debug("最大值:{}", collect.getMax());
log.debug("最小值:{}", collect.getMin());
log.debug("平均值:{}", collect.getAverage());
log.debug("统计数量:{}", collect.getCount());
log.debug("求和值:{}", collect.getSum());
注:在操作流的时候有很多情况是不能带有空值的,例如在统计时元素就不能有空值,在分组时key不能为空,否则会报错,针对这种情况可以在中间处理环节使用filter()过滤,或者用map()判别空值然后重新赛回一个非空的数据,如下面的统计例子。
List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5, null);
//Double collect = integers.stream()
//.collect(Collectors.summingDouble(item -> item)); //带有空值会报错
//log.debug("求和:{}",collect);
//为空的转为0
double sum1 = integers.stream()
.map(item -> ObjectUtils.isEmpty(item) ? 0 : item)
.mapToDouble(item -> item).sum();
//为空的过滤掉
double sum2 = integers.stream()
.filter(item -> ObjectUtils.isNotEmpty(item))
.mapToDouble(item -> item).sum();
log.debug("为空转为0:{},为空过滤:{}", sum1, sum2)
标签:Stream,Collectors,item,流在,collect,分组,User,stream,Java8
From: https://www.cnblogs.com/swz123/p/16990680.html