首页 > 编程语言 >Java8-Stream流在项目中的常用方式。

Java8-Stream流在项目中的常用方式。

时间:2022-12-18 17:57:08浏览次数:59  
标签:Stream Collectors item 流在 collect 分组 User stream Java8

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 filter(Predicate<? super T> predicate); 对集合中的元素进行过滤操作。
Stream limit(long maxSize); 对集合元素最大数量进行限制。
Stream skip(long n); 对集合元素进行跳过。
Stream distinct(); 对集合元素进行去重。
Stream map(Function<? super T, ? extends R> mapper); 简单来说就是对当前处理的元素进行处理后重新放回到集合中。
Stream flatMap(Function<? super T, ? extends Stream<? extends R>> mapper); 简单来说就是它能够将嵌套的集合进行发散处理,最后返回发散后的流。
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 findFirst(); 返回第一个
long count(); 返回流中元素的总个数
Optional max(Comparator<? super T> comparator); 传入一个比较器,返回最大元素

常用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

相关文章

  • composer报错failed to open stream: operation failed
    The“https://packagist.laravel-china.org/packages.json”filecouldnotbedownloaded:PeercertificateCN=*.phphub.org'didnotmatchexpectedCN=packagist.l......
  • JAVA8自带TreeUtils
        tree.json{"code":200,"msg":"操作成功","data":[{"id":"310000","name":"电子商务","parentId":"000000"}......
  • 5万字长文:Stream和Lambda表达式最佳实践-附PDF下载
    5万字长文详解介绍Stream和Lambda表达式最佳实践,干货实在太多,最后附上PDF下载,方便大家查阅!目录​1.Streams简介​​​1.1创建Stream​​​​1.2......
  • Java8之list.stream的常见使用
    本文转自 https://blog.csdn.net/jhgnqq/article/details/123679622感谢楼主分享importorg.junit.Before;importorg.junit.Test;importjava.util.Arrays;import......
  • [GStreamer] 版本更新历史
    ​重点关注关键字:appsrc    appsink        gst_init        Pluginremovals        MiscellaneousAPIadditions        Bindi......
  • 记录一下:Java8和Java11对sun.misc.BASE64Encoder的替换
    JDK8之后的版本中针对sun.misc.BASE64Encoder使用方法进行了修改升级,JDK8中:BASE64Decoderdecoder=newBASE64Decoder();byte[]bytes=decoder.decodeBuffer(str)......
  • Elasticsearch 入门实战(7)--Data Stream
    数据量(DataStream) 是在 Elasticsearch 7.9版推出的一项功能,它可以很方便的处理时间序列数据。1、简介1.1、什么是TimeSeriesDataTSD始终与时间戳关联,该时间戳标......
  • Java8:Lambdas(一) 学习lambda表达式
    了解Java8中的lambda表达式对开发人员来说没有什么比自己选择的语言或平台发布新版本更令人激动了。Java开发者也不例外。实际上,我们更期待新版本的发布,有一部分原因是因......
  • Java8:Lambdas(二)学习怎样去使用lambda表达式
    JavaSE8的发布很快就到了。伴随着它来的不仅仅是新的语言lambda表达式(同样被称为闭包或匿名方法)——伴随着一些语言特性支持——更重要的是API和library的增强将会使传统......
  • git push时提示--set-upstream
    问题:提示需要加--set-upstream 分析:git分支与远程主机存在对应分支,可能是单个可能是多个。 simple方式:如果当前分支只有一个追踪分支,那么gitpushorigin到主机时......