展开菜单
首页 精品内容 本月促销 装机必备 Windows PDF教程 macOS软件 IOS软件 Android AI 专题
全部分类

当前位置:

首页 > 编程开发 > 如何运用Java8函数式编程中的收集器?

如何运用Java8函数式编程中的收集器?

收集器收集器是一种通用的、从流生成复杂值的结构。可以使用它从流中生成List、Set、Map等集合。收集器都是在流的collect方法中调用,并且都在Collectors类中。java的标准类库提供了很多有用的收集器,当然了,也可以自己自定义(这个对于使用者的要求很高)。下面提供一个代码,用于测试接下里要说的收集器:提供了一个简单的测试数据学号姓名性别语文数学英语物理政治总分09509002节强男869090939009509003杨青女909082919209509006徐刚男7892839087095

本文目录

      收集器

      收集器是一种通用的、从流生成复杂值的结构。可以使用它从流中生成List、Set、Map等集合。 收集器都是在流的collect方法中调用,并且都在 Collectors类中。

      java 的标准类库提供了很多有用的收集器,当然了,也可以自己自定义(这个对于使用者的要求很高)。

      下面提供一个代码,用于测试接下里要说的收集器:

      提供了一个简单的测试数据

      学号 姓名 性别 语文 数学 英语 物理 政治 总分
      09509002 节强 男 86 90 90 93 90
      09509003 杨青 女 90 90 82 91 92
      09509006 徐刚 男 78 92 83 90 87
      09509111 马力 男 77 88 99 90 88
      09509001 武向丽 女 90 78 83 94 94
      09509007 张文静 女 85 90 79 94 88
      09509005 徐小红 女 78 85 88 93 92
      09509009 李姝 女 92 80 75 90 88
      09509004 李文华 男 68 59 70 85 90
      09509008 夏婧 女 87 65 73 91 95
      09509010 王洪 男 66 48 89 70 57

      Student 实体类封装数据

      package com.cdragon;
      
      public class Student implements Comparable {
          private String number;
          private String name;
          private String sex;
          private Integer chinese;
          private Integer math;
          private Integer english;
          private Integer physics;
          private Integer politics;
      	//省略getter和setter方法,这个使用IDE自动生成特别方便。
      	//省略toString方法,同上。
      	
      	@Override
          public int compareTo(Student s) {
              return s.getNumber().compareTo(this.getNumber());
          }
      }

      LoadData 类加载数据到内存

      package com.cdragon;
      
      import java.io.*;
      import java.util.ArrayList;
      import java.util.List;
      
      public class LoadData {
      
          public static List readFromFile(File file) {
              List students = new ArrayList<>();
              try (BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(file)))) {
                  String record = null;
                  String header = br.readLine();
                  //对于数据的第一行头,暂时不做处理。
                  while ((record = br.readLine() ) != null) {
                      Student s = resolveLineToStudent(record);
                      students.add(s);
                  }
              } catch (FileNotFoundException e) {
                  e.printStackTrace();
              } catch (IOException e) {
                  e.printStackTrace();
              }
              return students;
          }
      
          private static Student resolveLineToStudent(String record) {
              String[] array = record.split("\\s+");  // \\s 和 \\s+ 还是有区别的!
              Student s = new Student();
              s.setNumber(array[0]);
              s.setName(array[1]);
              s.setSex(array[2]);
              s.setChinese(Integer.parseInt(array[3]));
              s.setMath(Integer.parseInt(array[4]));
              s.setEnglish(Integer.parseInt(array[5]));
              s.setPhysics(Integer.parseInt(array[6]));
              s.setPolitics(Integer.parseInt(array[7]));
              return s;
          }
      
      }

      收集器应用

      将流转换成其他集合

      使用收集器是可以生成其他集合的,例如生成List、Set 和 Map等,下面来分别举例:

      		//生成 List
              List studentList = students.stream().collect(Collectors.toList());
              //生成 Set
              Set studentSet = students.stream().collect(Collectors.toSet());
              //生成指定集合
              TreeSet studentTreeSet = students.stream().collect(Collectors.toCollection(TreeSet::new));
              //生成 Map 
              Map studentMap = students.stream().collect(Collectors.toMap(Student::getNumber, s->s));
      
              studentMap.forEach((no, s)->{
                  System.out.println(no + "->" + s);
              });

      说明:通常的 toList() 和 toSet() 方法是不指定生成集合的具体类型,这是由系统来选择最合适的类型,但是有时候我们必须返回特定的类型集合,这就用到了 toCollection() 方法,这个方法可以指定需要生成的集合的类型,这是使用方法引用进行简化代码:TreeSet::new

      测试结果:

      如何运用Java8函数式编程中的收集器?

      注意:生成 Map 的方式较为复杂,因为需要同时指定键和值。

      转换成值

      使用收集器生成一个值。

      最大值和最小值

      Collectors 类中的 maxBy 和 minBy 允许用户按照某种特定顺序生成一个值。它们的作用就如同它们的名字一样,分别是寻找最大值和最小值。

      我写成一个方法,这样调用比较方便。

      /**
       * 获取单科最高分。
       * */
      public static Optional minOrMaxSubject(List students, Comparator comparator) {
          return students.stream().collect(Collectors.maxBy(comparator));
      }

      说明:使用 maxBy 或者 minBy 必须传入一个 Comparator 对象作为参数,即参数为一个比较器。

      测试代码

      //这个文件的路径应该使用自己指定的
      List students = LoadData.readFromFile(new File("src/grade.txt"));
      //获取数学最高分学生
      Optional s1 = TestStream.minOrMaxSubject(students, Comparator.comparing(Student::getMath));
      //获取英语最高分
      Optional s2 = TestStream.minOrMaxSubject(students, Comparator.comparing(Student::getEnglish));
      System.out.println(s1.get());
      System.out.println(s2.get());

      测试结果

      如何运用Java8函数式编程中的收集器?

      说明:如果想要测试最低分,只要把上面的 maxBy 改成 minBy 就行了,或者直接更进一步,修改参数为collect里面传入的函数,不过那样就会显得格外复杂,而且不止可以查最高分和最低分了。

      平均值

      上面看过了最大值和最小值,现在来看看平均值。 下面这个方法是用来求单科平均分的。

      /**
       * 获取单科平均分
       * */
       public static double averageScore(List students, ToIntFunction mapper) {
           return students.stream().collect(Collectors.averagingInt(mapper));
       }

      测试代码

      List students = LoadData.readFromFile(new File("src/grade.txt"));
      double math = TestStream.averageScore(students, Student::getMath);
      System.out.println("数学的单科平均分:" + math);

      测试结果

      如何运用Java8函数式编程中的收集器?

      数据分块

      数据分块是指收集器将流分为两个集合,注意分块是只能分成两块。这里标准类库提供了一个收集器 partitioningBy,它接受一个流,并将其分为两个部分。返回的结果为一个 Map,键只有两种:true 或者 false,值是满足对应条件的集合。

      例如我想知道某们成绩 90分以上和一下的学生分别是哪些。

      /**
       * 以特定分数划分不同学生,例如90分以上(含90分)和90分一下。
       * 结果是一个Map集合,只有两个元素,true  false 个对应一个集合。
       * */
       public static Map> splitScore(List students, Predicate predicate) {
           return students.stream().collect(Collectors.partitioningBy(predicate));
       }

      说明:partitioningBy的参数为一个 Predicate 对象,这个和过滤器的很相似,功能上可以对比学习。

      测试代码

      //数学成绩以90分来划分学生
      Map> booleanListMap = TestStream.splitScore(students, stu->stu.getMath()>=90);
      booleanListMap.forEach((bool, list)->{
          System.out.println("数学成绩大于90分:" + bool);
          list.forEach(System.out::println);
          System.out.println("========================");
      });

      测试结果

      如何运用Java8函数式编程中的收集器?

      数据分组

      数据分组是一种更为自然的分割数据操作,与将数据分成true和false两部分不同,可以使用任意值对数据分组。比如使用性别对学生进行分组。这很像SQL中的 groupBy 操作。

      /**
      * 数据分组
      * 这里以性别来分组
      * */
      public static Map> groupBy(List students) {
          return students.stream().collect(Collectors.groupingBy(Student::getSex));
      }

      测试代码

      Map> stringListMap = TestStream.groupBy(students);
      stringListMap.forEach((sex, list)->{
          System.out.println("性别:" + sex);
          list.forEach(System.out::println);
          System.out.println("============");
      });

      测试结果

      如何运用Java8函数式编程中的收集器?

      字符串

      收集流中的数据最后生成一个字符串,这是一个很平常的操作。 例如一个所有学生的姓名列表,使用传统的迭代列表操作代码如下:

       /**
        * 获取所有学生姓名的字符串
        * 传统的迭代操作
        * 格式如下:[张三,李四]
        * */
       public static String nameStr1(List students) {
           StringBuilder builder = new StringBuilder("[");
           for (Student stu : students) {
               if (builder.length() > 1){
                   builder.append(",");
               }
               String name = stu.getName();
               builder.append(name);
           }
           builder.append("]");
           return builder.toString();
       }

      然后是使用收集器进行操作,代码如下: 这里我添加一些细节处理,学生的排名按照学生的总成绩从高到底排列,这是很符合习惯的。

      /**
       * 获取所有学生姓名的字符串
       * 函数式方法
       * 格式如下:[张三,李四]
       *
       * 注意,他只能连接字符串,所有这里使用 map 操作,将 Student 转成 String(学生姓名)
       * */
      public static String nameStr2(List students) {
          return students.stream()
                  .sorted(Comparator.comparing(s -> {
                      return s.getChinese() + s.getMath() + s.getEnglish()
                              + s.getPhysics() + s.getPolitics();
                  }, Comparator.reverseOrder()))  //(sum1, sum2)-> sum2.compareTo(sum1)
                  .map(Student::getName)
                  .collect(Collectors.joining(",","[","]"));
      }

      说明:这里的 sorted 需要传入一个 Comparator 对象,但是可以使用静态方法 Compring 进行简化,但是它只是指定需要排序的标准,并没有说是从小到大还是从大到小,后来才发现,这个是默认的:大小到大排序。但是我需要的是使用从大到小,然后发现原来 compring 还有重载方法,具有两个参数,另一个参数是可以指定大小顺序的,所以第二个参数我传入了一个 Lambda 表达式:

      (sum1, sum2)-> sum2.compareTo(sum1)

      但是如果这样使用的话,还不如直接使用 Lambda 表达式创建 Comparator 对象方便呢,后来发现这个 IDE 比较智能,它指出这句代码,可以被替换为:

      Comparator.reverseOrder();// 看意思就知道是 反序的意思。

      这样看来使用 Comparator 静态的 comparing 方法还是比直接创建 Comparator 对象简单一些。

      注意:如果不需要排序的话,就只有一个map方法和join方法了。这个map方法的作用是映射(我一开始把它和map集合总是搞混了),将Student对象映射为name字符串,然后使用 join 方法进行连接。

      组合收集器

      收集器还可以组合起来使用,这个和 SQL 感觉更像了,几乎具有函数式编程的语言,都有SQL那种处理数据的方式,例如最大值、最小值和分组等操作。 考虑对于学生按照性别分组,然后再分别统计男女生的人数。(这个在 SQL 里面也是一个基本的练习。)

      /**
       * 组合收集器
       * 这里以性别来分组,再分别计数
       * */
      public static Map combination(List students) {
          return students.stream().collect(Collectors.groupingBy(Student::getSex, Collectors.counting()));
      }

      测试代码

      Map stringLongMap = TestStream.combination(students);
      stringLongMap.forEach((sex, count)->{
          System.out.println("性别:" + sex + ", 人数:" + count);
      });

      测试结果

      如何运用Java8函数式编程中的收集器?

      使用流的其他操作

      对于流的使用,应该达到一个较为熟练的地步,但是由于没有什么机会实践,还是比较陌生。下面介绍几个我写的方法,来看看流的操作:

      //通过过滤器选择特定的学生,过滤器用于过滤,然后选择第一个学生。
      //这里应该加一个排序操作比较好。
      public static Optional selectStudent(List students, Predicate pre) {
              return students.stream().filter(pre).findFirst();
          }
      
      public static List orderBy(List students, Comparator comparator) {
          if (comparator != null){
              return students.stream().sorted(comparator).collect(Collectors.toList());
          } else {
              return students.stream().sorted().collect(Collectors.toList());
          }
      }
      
      //获取一列数据。不是一行学生记录,是一列。
      public static List getAColumn(List students, Function mapper) {
          return students.stream().map(mapper).collect(Collectors.toList());
      }
      
      /**
       * 获取所有学生的总分和学号
       * */
      public static Map getSum(List students) {
          return students.stream().collect(Collectors.toMap(Student::getNumber, stu->{
              return stu.getChinese() + stu.getEnglish() + stu.getMath() + stu.getPhysics() + stu.getPolitics();
          }));
      }
      
      
      /**
       * peek 和 forEach 的区别
       * peek 是一个中间操作,forEach 是一个终结操作。
       *
       * 假如实现一个功能:每个学生的某门科目分数进行修改。
       *
       * peek 操作后得到的仍然是一个 stream,此时可以进一步操作,
       * 但是 forEach 是终结操作,操作结束,流就结束了,如果需要进一步处理,
       * 必须再次进行得到流的操作。
       * */
      
      public static List addScore1(List students, Consumer action) {
          return students.stream().peek(action).collect(Collectors.toList());
      }
      
      public static void addScore2(List students, Consumer action) {
          students.stream().forEach(action);
      }
      
      //指定返回类型为 LinkedList,这时一个测试,并不是说需要这样写。
      //多数情况下,我们还是应该使用 ArrayList
      public static List addScore3(List students, Consumer action) {
          return students.stream().peek(action).collect(Collectors.toCollection(LinkedList::new));
      }

      对于其中的几个进行测试(不是全部方法,如果感兴趣,可以自己尝试。):

      //对于学生进行排序,参数为一个比较器,参数为空的话,使用默认的 sorted 排序。
      //测试代码 按照学号排序(默认从小到大)
      TestStream.orderBy(students,Comparator.comparing(Student::getNumber)).forEach(System.out::println);
      
      //按照学号排序(从大到小)
      TestStream.orderBy(students,Comparator.comparing(Student::getNumber, Comparator.reverseOrder())).forEach(System.out::println);
      
      //使用默认的排序
      TestStream.orderBy(students).forEach(System.out::println);
      
      //获取一列学生的记录,例如这里是英语成绩,这里返回值我使用通配符应该没有错吧
      //因为返回数据可能为 String 也可能是 Integer
      TestStream.getAColumn(students,Student::getEnglish).forEach(System.out::println);
      
      //测试学生的总分
      TestStream.getSum(students).forEach((no, stu)->{
          System.out.println(no + " -> " + stu);
      });
      本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系bd@zhengruan.com
      作者最新文章
      编程开发 Java
      相关文章 更多
      精品专题 更多
      装机必备

      正软商城装机必备专区,精选办公、浏览器、安全防护、影音播放、压缩解压、设计创作和系统工具等电脑常用正版软件,帮助用户快速完成新电脑软件配置。

      Windows

      正软商城Windows软件专区,汇集适用于Windows电脑的办公、设计、安全防护、影音播放、开发工具和系统优化软件,提供软件介绍、系统要求、正版授权及购买下载服务。

      PDF教程

      正软商城PDF教程频道提供PDF编辑、转换、合并、拆分、压缩及格式处理方法,同时介绍常用PDF软件和工具的使用技巧。

      macOS软件

      正软商城macOS软件专区,精选适用于Mac电脑的办公、设计、影音、效率、开发和系统工具,提供软件功能介绍、macOS兼容版本、正版授权及购买下载服务。

      IOS软件

      正软商城iOS软件专区,精选适用于iPhone和iPad的办公、学习、影音、设计、效率及AI应用,提供功能介绍、适用设备、系统要求和正版获取方式等信息。

      AI

      正软商城AI软件专区,汇集AI写作、AI绘画、AI视频、AI办公、AI编程、AI翻译、智能客服和数据分析等人工智能工具,提供功能介绍、适用平台、收费方式及正版购买信息。

      Mac软件 更多
      Aperty macOS版
      Aperty macOS版

      Aperty 是 Skylum 推出的 AI 人像修图软件,支持自然肤质修饰、瑕疵处理、面部与身体调整、预设及批量同步。

      火山引擎 网页版
      火山引擎 网页版

      火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

      立刻MV 网页版
      立刻MV 网页版

      立刻MV 面向音乐人、内容创作者和品牌团队,帮助用户低成本完成歌曲 MV、歌词视频、AI 分镜、图生视频与最终合成。

      蛙蛙写作 网页版
      蛙蛙写作 网页版

      蛙蛙写作 AI 创作工具,覆盖小说、剧本、文案,一键生成文稿,打通文字转视频全流程,高效解决创作难题。

      讯飞智作 网页版
      讯飞智作 网页版

      讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

      超级简历WonderCV 网页版
      超级简历WonderCV 网页版

      超级简历WonderCV是面向应届生和AI小白的智能求职平台,覆盖简历、面试与职业规划。

      音述AI 网页版
      音述AI 网页版

      音述AI,是一个以“用声音述说故事”为核心的 AI 音乐创作与声音分享社区。

      AionClaw macOS版
      AionClaw macOS版

      AionClaw 是可在桌面端运行的任务型 AI 助手,通过数字专家与技能市场组合办公、创作、数据和开发能力。

      Loomy macOS版
      Loomy macOS版

      Loomy 是科大讯飞推出的桌面 AI 助手,可通过对话、任务与工作目录协助处理办公、内容创作和资料整理。

      AirBuddy 3 macOS版
      AirBuddy 3 macOS版

      AirBuddy 3 是面向 macOS 的蓝牙外设增强工具,可集中查看 AirPods、Beats 与苹果设备电量,提供快速连接、播放控制、电量提醒、桌面小组件和多台 Mac 外设接力等功能。

      AirServer macOS版
      AirServer macOS版

      AirServer macOS版可把 Mac 转换为无线投屏接收端,通过 AirPlay 或 Google Cast 接收手机、平板和电脑画面,并支持多设备同时连接、高清镜像及会话录制,适合家庭娱乐、课堂教学、会议演示和应用展示。

      AnyDesk macOS版
      AnyDesk macOS版

      AnyDesk macOS版是一款适用于远程办公、设备维护和客户支持的远程桌面工具,可连接并操作异地计算机,支持文件传输、无人值守访问、会话权限控制及跨平台协作。

      WINDOWS 更多
      讯飞绘文 网页版
      讯飞绘文 网页版

      讯飞绘文网页版是科大讯飞推出的AI图文创作与内容运营工具,覆盖热点选题、文章生成、稿件校对、智能配图、排版发布和数据复盘等环节,适合自媒体创作者及内容团队在线使用。

      Maya(Autodesk Maya) Windows版
      Maya(Autodesk Maya) Windows版

      Autodesk Maya 是面向影视、电视、动画和游戏行业的专业三维制作软件,集多边形与 NURBS 建模、角色绑定、关键帧和动作捕捉动画、Bifrost 特效模拟、材质开发及 Arnold 渲染于一体,

      Aperty Windows版
      Aperty Windows版

      Aperty 是 Skylum 推出的 AI 人像修图软件,支持自然肤质修饰、瑕疵处理、面部与身体调整、预设及批量同步。

      火山引擎 网页版
      火山引擎 网页版

      火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

      立刻MV 网页版
      立刻MV 网页版

      立刻MV 面向音乐人、内容创作者和品牌团队,帮助用户低成本完成歌曲 MV、歌词视频、AI 分镜、图生视频与最终合成。

      蛙蛙写作 网页版
      蛙蛙写作 网页版

      蛙蛙写作 AI 创作工具,覆盖小说、剧本、文案,一键生成文稿,打通文字转视频全流程,高效解决创作难题。

      讯飞智作 网页版
      讯飞智作 网页版

      讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

      超级简历WonderCV 网页版
      超级简历WonderCV 网页版

      超级简历WonderCV是面向应届生和AI小白的智能求职平台,覆盖简历、面试与职业规划。

      音述AI 网页版
      音述AI 网页版

      音述AI,是一个以“用声音述说故事”为核心的 AI 音乐创作与声音分享社区。

      AionClaw Windows版
      AionClaw Windows版

      AionClaw 是可在桌面端运行的任务型 AI 助手,通过数字专家与技能市场组合办公、创作、数据和开发能力。

      Loomy Windows版
      Loomy Windows版

      Loomy 是科大讯飞推出的桌面 AI 助手,可通过对话、任务与工作目录协助处理办公、内容创作和资料整理。

      AirServer Windows版
      AirServer Windows版

      AirServer Windows版可把电脑变成AirPlay、Google Cast和Miracast无线投屏接收端,适合会议演示、课堂教学及家庭大屏分享。发送设备通常无需另装客户端,并可同时接收和智能排列多个镜像会话。