一文详解Java中的Stream的汇总和分组操作

目录
  • 前言
  • 一、查找流中的最大值和最小值
  • 二、汇总
  • 三、连接字符串
  • 四、分组
    • 1、分组
    • 2、多级分组
    • 3、按子组数据进行划分
  • 后记

前言

在前面的文章中其实大家也已经看到我使用过collect(Collectors.toList()) 将数据最后汇总成一个 List 集合。

但其实还可以转换成Integer、Map、Set 集合等。

一、查找流中的最大值和最小值

     static List<Student> students = new ArrayList<>();
 ​
     static {
         students.add(new Student("学生A", "大学A", 18, 98.0));
         students.add(new Student("学生B", "大学A", 18, 91.0));
         students.add(new Student("学生C", "大学A", 18, 90.0));
         students.add(new Student("学生D", "大学B", 18, 76.0));
         students.add(new Student("学生E", "大学B", 18, 91.0));
         students.add(new Student("学生F", "大学B", 19, 65.0));
         students.add(new Student("学生G", "大学C", 20, 80.0));
         students.add(new Student("学生H", "大学C", 21, 78.0));
         students.add(new Student("学生I", "大学C", 20, 67.0));
         students.add(new Student("学生J", "大学D", 22, 87.0));
     }
 ​
     public static void main(String[] args) {
         Optional<Student> collect1 = students.stream().collect(Collectors.maxBy((s1, s2) -> s1.getAge() - s2.getAge()));
         Optional<Student> collect2 = students.stream().collect(Collectors.minBy((s1, s2) -> s1.getAge() - s2.getAge()));
         Student max = collect1.get();
         Student min = collect2.get();
         System.out.println("max年龄的学生==>" + max);
         System.out.println("min年龄的学生==>" + min);
         /**
          * max年龄的学生==>Student(name=学生J, school=大学D, age=22, score=87.0)
          * min年龄的学生==>Student(name=学生A, school=大学A, age=18, score=98.0)
          */
     }

Optional,它是一个容器,可以包含也可以不包含值。它是java8中人们常说的优雅的判空的操作。

另一个常见的返回单个值的归约操作是对流中对象的一个数值字段求和。或者你可能想要求 平均数。这种操作被称为汇总操作。让我们来看看如何使用收集器来表达汇总操作。

二、汇总

Collectors类专门为汇总提供了一些个工厂方法:

当然除此之外还有 求平均数averagingDouble、求总数counting等等

我们暂且就先以summingDoublesummarizingDouble来举例吧

案例数据仍然是上面的那些student数据...

求全部学生成绩的总分,求全部学生的平均分。

1、首先使用summingDoubleaveragingDouble 来实现

 Double summingScore = students.stream().collect(Collectors.summingDouble(Student::getScore));
 Double averagingScore = students.stream().collect(Collectors.averagingDouble(Student::getScore));
 System.out.println("学生的总分==>" + summingScore);
 System.out.println("学生的平均分==>" + averagingScore);
 /**
 * 学生的总分==>823.0
  * 学生的平均分==>82.3
 */

2、使用summarizingDouble来实现

它更为综合,可以直接计算出相关的汇总信息

 DoubleSummaryStatistics summarizingDouble = students.stream().collect(Collectors.summarizingDouble(Student::getScore));
 ​
 double sum = summarizingDouble.getSum();
 long count = summarizingDouble.getCount();
 double average = summarizingDouble.getAverage();
 double max = summarizingDouble.getMax();
 double min = summarizingDouble.getMin();
 System.out.println("sum==>"+sum);
 System.out.println("count==>"+count);
 System.out.println("average==>"+average);
 System.out.println("max==>"+max);
 System.out.println("min==>"+min);
 /**
  * sum==>823.0
  * count==>10
  * average==>82.3
  * max==>98.0
  * min==>65.0
  */

但其实大家也都发现了,使用一个接口能够实现,也可以拆开根据自己的所需,选择合适的API来实现,具体的使用还是需要看使用场景。

三、连接字符串

Joining,就是把流中每一个对象应用toString方法得到的所有字符串连接成一个字符串。

如果这么看,它其实没啥用,但是Java也留下了后招,它的同伴(重载方法)提供了一个可以接受元素之间的分割符的方法。

 ​
         String studentsName = students.stream().map(student -> student.getName()).collect(Collectors.joining());
         System.out.println(studentsName);
         String studentsName2 = students.stream().map(student -> student.getName()).collect(Collectors.joining(","));
         System.out.println(studentsName2);
         /**
          * 学生A学生B学生C学生D学生E学生F学生G学生H学生I学生J
          * 学生A,学生B,学生C,学生D,学生E,学生F,学生G,学生H,学生I,学生J
          */

对于对象的打印:

 // 不过对于对象的打印 个人感觉还好 哈哈
 String collect = students.stream().map(student -> student.toString()).collect(Collectors.joining(","));
 System.out.println(collect);
 System.out.println(students);
 /**
          * Student(name=学生A, school=大学A, age=18, score=98.0),Student(name=学生B, school=大学A, age=18, score=91.0),Student(name=学生C, school=大学A, age=18, score=90.0),Student(name=学生D, school=大学B, age=18, score=76.0),Student(name=学生E, school=大学B, age=18, score=91.0)....
          * [Student(name=学生A, school=大学A, age=18, score=98.0), Student(name=学生B, school=大学A, age=18, score=91.0), Student(name=学生C, school=大学A, age=18, score=90.0), Student(name=学生D, school=大学B, age=18, score=76.0)..)]
          */

但其实我还有一些没有讲到的API使用方法,大家也可以额外去尝试尝试,这其实远比你看这篇文章吸收的更快~

四、分组

就像数据库中的分组统计一样~

1、分组

举个例子,我想统计每个学校有哪些学生

我是不是得设计这样的一个数据结构Map<String,List<Student>>才能存放勒,我在循环的时候,是不是每次都得判断一下学生所在的学校的名称,然后看是否要给它添加到这个List集合中去,最后再put到map中去呢?

看着就特别繁琐,但是在 stream 中就变成了一行代码,其他的东西,都是 Java 内部给你优化了

         // 我想知道每所学校中,学生的数量及相关信息,只要这一行代码即可
         Map<String, List<Student>> collect = students.stream().collect(Collectors.groupingBy(Student::getSchool));
         System.out.println(collect);
         /**
          * {大学B=[Student(name=学生D, school=大学B, age=18, score=76.0), Student(name=学生E, school=大学B, age=18, score=91.0), Student(name=学生F, school=大学B, age=19, score=65.0)],
          * 大学A=[Student(name=学生A, school=大学A, age=18, score=98.0), Student(name=学生B, school=大学A, age=18, score=91.0), Student(name=学生C, school=大学A, age=18, score=90.0)],
          * 大学D=[Student(name=学生J, school=大学D, age=22, score=87.0)],
          * 大学C=[Student(name=学生G, school=大学C, age=20, score=80.0), Student(name=学生H, school=大学C, age=21, score=78.0), Student(name=学生I, school=大学C, age=20, score=67.0)]}
          */

有些时候这真的是十分有用且方便的

但是有时候我们往往不止于如此,假如我要统计每个学校中20岁年龄以上和20以下的学生分别有哪些学生,那么我的参数就不再是Student::getSchool了,而是要加上语句了。那么该如何编写呢

 //统计每个学校中20岁年龄以上和20以下的学生分别有多少
 Map<String, List<Student>> collect = students.stream().collect(Collectors.groupingBy(student -> {
     if (student.getAge() > 20) {
         return "20岁以上的";
     }
     return "20以下的";
 }));
 System.out.println(collect);

如果要统计每个学校有多少20岁以上和20岁以下的学生的信息,其实也就是把 return 语句修改以下即可。

 //统计每个学校中20岁年龄以上和20以下的学生分别有多少
 Map<String, List<Student>> collect = students.stream().collect(Collectors.groupingBy(student -> {
     if (student.getAge() > 20) {
         return student.getSchool();
     }
     return student.getSchool();
 }));
 System.out.println(collect);

相信大家也看出来groupingBy中的 return 语句就是 Map 中的key值

2、多级分组

但其实groupingBy()并不只是一个人,它也有兄弟姐妹

假如我想把上面的例子再改造改造,

改为:我想知道20岁以上的学生在每个学校有哪些学生,20岁以下的学生在每个学校有哪些学生。

数据结构就应当设计为Map<String, Map<String, List<Student>>> 啦,第一级存放 20岁以上以下两组数据,第二级存放以每个学校名为key的数据信息。

 Map<String, Map<String, List<Student>>> collect = students.stream().collect(Collectors.groupingBy(Student::getSchool, Collectors.groupingBy(student -> {
     if (student.getAge() > 20) {
         return "20以上的";
     }
     return "20岁以下的";
 })));
 System.out.println(collect);
 /**
          * {大学B={20岁以下的=[Student(name=学生D, school=大学B, age=18, score=76.0),Student(name=学生E, school=大学B, age=18, score=91.0), Student(name=学生F, school=大学B, age=19, score=65.0)]},
          * 大学A={20岁以下的=[Student(name=学生A, school=大学A, age=18, score=98.0), Student(name=学生B, school=大学A, age=18, score=91.0), Student(name=学生C, school=大学A, age=18, score=90.0)]},
          * 大学D={20以上的=[Student(name=学生J, school=大学D, age=22, score=87.0)]},
          * 大学C={20以上的=[Student(name=学生H, school=大学C, age=21, score=78.0)],20岁以下的=[Student(name=学生G, school=大学C, age=20, score=80.0), Student(name=学生I, school=大学C, age=20, score=67.0)]}}
          */

这里利用的就是把一个内层groupingBy传递给外层groupingBy,俗称的套娃~

外层Map的键就是第一级分类函数生成的值,而这个Map的值又是一个Map,键是二级分类函数生成的值。

3、按子组数据进行划分

之前我的截图中,groupingBy的重载方法中,其实对于第二个参数的限制,并非说一定是要groupingBy类型的收集,更抽象点说,它可以是任意的收集器~

再假如,我的例子改为:

我现在明确的想知道每个学校20岁的学生的人数。

那么这个数据结构就应当改为

Map<String,Long>或者是Map<String,Integer>呢?

那么在这里该如何实现呢?

 Map<String, Long> collect = students.stream().collect(Collectors.groupingBy(Student::getSchool, Collectors.counting()));
 System.out.println(collect);
 /**
 * {大学B=3, 大学A=3, 大学D=1, 大学C=3}
 */

实际上还有许多未曾谈到的东西,这里都只是非常简单的应用,对于其中的流的执行的先后顺序,以及一些简单的原理,都没有过多的涉及,大家先上手用着吧~

后记

我这里只是阐述了一些比较简单的应用性操作,未谈及设计思想之类,但是要明白那种才是更值得去阅读和理解的。

到此这篇关于一文详解Java中的Stream的汇总和分组操作的文章就介绍到这了,更多相关Java Stream汇总 分组内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java8中Stream的详细使用方法大全

    目录 一.概述 1.使用流的好处 2.流是什么? 二.分类 三.Stream的创建 1.通过 java.util.Collection.stream() 方法用集合创建流 2.使用 java.util.Arrays.stream(T[]array)方法用数组创建流 3.使用 Stream的静态方法:of().iterate().generate() 四.Stream API简介 1.遍历/匹配(foreach/find/match) 2.按条件匹配filter 3.聚合max.min.count

  • Java基础之Stream流原理与用法详解

    目录 一.接口设计 二.创建操作 三.中间操作 四.最终操作 五.Collect收集 Stream简化元素计算 一.接口设计 从Java1.8开始提出了Stream流的概念,侧重对于源数据计算能力的封装,并且支持序列与并行两种操作方式:依旧先看核心接口的设计: BaseStream:基础接口,声明了流管理的核心方法: Stream:核心接口,声明了流操作的核心方法,其他接口为指定类型的适配: 基础案例:通过指定元素的值,返回一个序列流,元素的内容是字符串,并转换为Long类型,最终计算求和结果并

  • java Stream流常见操作方法(反射,类加载器,类加载,反射)

    目录 Stream流常见的中间操作方法 Stream流中常见的终结操作方法 反射 类加载器 反射概述 Stream流常见的中间操作方法 Streamfilter(Predicate predicate):用于对流中的数据进行过滤 predicate接口中的方法 boolean test(T t):对给定的参数进行判断,返回一个布尔值 Stream limit(long maxSize):返回此流中元素组成的流,截取前指定参数个数的数据 Stream skip(long n) :跳过指定参数个数据

  • Java8 Stream流多字段求和、汇聚的实例

    目录 Stream流多字段求和.汇聚 实现方法 对象类型数据处理 Map类型数据处理 Stream分组求和使用笔记 分组求和使用 Stream流多字段求和.汇聚 实现方法 利用 Collectors.toMap(Function keyMapper, Function valueMapper, BinaryOperator mergeFunction) keyMapper:代表你最终想要获得的Map<Key, Value> 的Key valueMapper:代表你最终想要获得的Map<K

  • Java中DataInputStream和DataOutputStream的使用方法

    目录 简介 DataOutputStream DataInputStream 栗子1:写入数据 栗子2:读取 栗子3:保存学生信息 简介 在 io 包中,提供了两个与平台无关的数据操作流:数据输出流(DataOutputStream).数据输入流 (DataInputStream). 通常数据输出流会按照一定的格式将数据输出,再通过数据输入流按照一定的格式将数据读入.DataOutputStream 和 DataOutputStream 用来读写固定字节格式数据. DataOutputStrea

  • Java 中如何使用 stream 流

    目录 前言 一.筛选和切片 1.1.筛选 filter 1.2.去重 distinct 1.3.切片 limit 1.4.跳过元素 skip 1.5.排序 sorted 1.6.小结与综合应用 二.映射 map 三.查找和匹配 3.1.匹配 anyMatch.allMatch和noneMatch 方法 3.2.查找 findAny 与 findFirst 3.3.小结 四.归约 4.1.元素求和 reduce 后记 前言 如果你了解过 Liunx ,了解过 Liunx 的中管道命令 | ,那么你

  • Java中ByteArrayInputStream和ByteArrayOutputStream用法详解

    目录 ByteArrayInputStream ByteArrayOutputStream ByteArrayInputStream 介绍ByteArrayInputStream 是字节数组输入流.它继承于 InputStream. InputStream 通过read()向外提供接口,供它们来读取字节数据:而 ByteArrayInputStream 的内部额外的定义了一个计数器,它被用来跟踪 read() 方法要读取的下一个字节. 它包含一个内部缓冲区,该缓冲区包含从流中读取的字节.也就是说

  • java使用stream判断两个list元素的属性并输出方式

    目录 使用stream判断两个list元素的属性并输出 stream判断列表是否包含某几个元素/重复元素 代码SHOW Java stream判断列表是否包含重复元素 使用stream判断两个list元素的属性并输出 /** * 使用stream判断两个list中元素不同的item */ @Test public void test1(){ List<Param> stringList1 = new LinkedList<Param>(){{ add(new Param(1,&qu

  • Java 8中 Stream小知识小技巧方法梳理

    目录 前言 只能遍历的一次 Stream 那么为什么流只能遍历一次呢? 流操作 中间操作 终端操作 前言 上篇只是简单的动手操作操作了流(stream),那 stream 到底是什么呢? 官方的简短定义:“从支持数据处理操作的源生成的元素序列” 分成三部分: 元素序列:你可以简单将它类比于一样,不过集合说的是数据的集合,而 stream 重点在于表达计算.如我们之前说到的 filter.map.sorted.limit等等 源:昨天我提到,如果了解过 Liunx 管道命令的朋友们,会知道,Liu

  • 一文详解Java中的Stream的汇总和分组操作

    目录 前言 一.查找流中的最大值和最小值 二.汇总 三.连接字符串 四.分组 1.分组 2.多级分组 3.按子组数据进行划分 后记 前言 在前面的文章中其实大家也已经看到我使用过collect(Collectors.toList()) 将数据最后汇总成一个 List 集合. 但其实还可以转换成Integer.Map.Set 集合等. 一.查找流中的最大值和最小值 static List<Student> students = new ArrayList<>(); ​ static

  • 一文详解Java中Stream流的使用

    目录 简介 操作1:创建流 操作2:中间操作 筛选(过滤).去重 映射 排序 消费 操作3:终止操作 匹配.最值.个数 收集 规约 简介 说明 本文用实例介绍stream的使用. JDK8新增了Stream(流操作) 处理集合的数据,可执行查找.过滤和映射数据等操作. 使用Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询.可以使用 Stream API 来并行执行操作. 简而言之,Stream API 提供了一种高效且易于使用的处理数据的方式. 特点 不是数据结构

  • 一文详解Java中的类加载机制

    目录 一.前言 二.类加载的时机 2.1 类加载过程 2.2 什么时候类初始化 2.3 被动引用不会初始化 三.类加载的过程 3.1 加载 3.2 验证 3.3 准备 3.4 解析 3.5 初始化 四.父类和子类初始化过程中的执行顺序 五.类加载器 5.1 类与类加载器 5.2 双亲委派模型 5.3 破坏双亲委派模型 六.Java模块化系统 一.前言 Java虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验.转换解析和初始化,最 终形成可以被虚拟机直接使用的Java类型,这个过程

  • 一文详解Java中字符串的基本操作

    目录 一.遍历字符串案例 二.统计字符次数案例 三.字符串拼接案例 四.字符串反转案例 五.帮助文档查看String常用方法 一.遍历字符串案例 需求:键盘录入一个字符串,使用程序实现在控制台遍历该字符串 思路: 1.键盘录入一个字符串,用 Scanner 实现 2.遍历字符串,首先要能够获取到字符串中的每一个字符 public char charAt(int index):返回指定索引处的char值,字符串的索引也是从0开始的 3.遍历字符串,其次要能够获取到字符串的长度 public int

  • 一文详解Java中流程控制语句

    目录 概述 判断语句 if if...else if..else if...else if语句和三元运算符的互换 选择语句 switch case的穿透性 循环语句 for while do...while for 和 while 的小区别 跳出语句 break continue 死循环 嵌套循环 概述 在一个程序执行的过程中,各条语句的执行顺序对程序的结果是有直接影响的.也就是说,程序的流程对运行结果有直接的影响.所以,我们必须清楚每条语句的执行流程.而且,很多时候我们要通过控制语句的执行顺序

  • 详解Java中的reactive stream协议

    背景 每个数据流都有一个生产者一个消费者.生产者负责产生数据,而消费者负责消费数据.如果是同步系统,生产一个消费一个没什么问题.但是如果在异步系统中,就会产生问题. 因为生产者无法感知消费者的状态,不知道消费者到底是繁忙状态还是空闲状态,是否有能力去消费更多的数据. 一般来说数据队列的长度都是有限的,即使没有做限制,但是系统的内存也是有限的.当太多的数据没有被消费的话,会导致内存溢出或者数据得不到即使处理的问题. 这时候就需要back-pressure了. 如果消息接收方消息处理不过来,则可以通

  • 一文详解Java线程中的安全策略

    目录 一.不可变对象 二.线程封闭 三.线程不安全类与写法 四.线程安全-同步容器 1. ArrayList -> Vector, Stack 2. HashMap -> HashTable(Key, Value都不能为null) 3. Collections.synchronizedXXX(List.Set.Map) 五.线程安全-并发容器J.U.C 1. ArrayList -> CopyOnWriteArrayList 2.HashSet.TreeSet -> CopyOnW

  • 详解Java中Math.round()的取整规则

    做Java的面试题时遇到了以下这题,百度了一下Math.round()的修约规则,有的说是四舍五入,有的说是四舍六入,发现和我学分析化学时用的数字修约规则(四舍六入五成双)很像,所以验证一下: 原题:Math.round(11.5) 等于多少?Math.round(-11.5)等于多少? 作者给的解题方法如下: 答:Math.round(11.5)的返回值是12,Math.round(-11.5)的返回值是-11.四舍五入的原理是在参数上加0.5然后进行下取整. 先说结论,题目作者给的解释是对的

  • 详解Java中NullPointerException异常的原因详解以及解决方法

    NullPointerException是当您尝试使用指向内存中空位置的引用(null)时发生的异常,就好像它引用了一个对象一样. 当我们声明引用变量(即对象)时,实际上是在创建指向对象的指针.考虑以下代码,您可以在其中声明基本类型的整型变量x: int x; x = 10; 在此示例中,变量x是一个整型变量,Java将为您初始化为0.当您在第二行中将其分配给10时,值10将被写入x指向的内存中. 但是,当您尝试声明引用类型时会发生不同的事情.请使用以下代码: Integer num; num

  • 详解Java 中的 AutoCloseable 接口

    一.前言 最近用到了 JDK 7 中的新特性 try-with-resources 语法,感觉到代码相对简洁了很多,于是花了点时间详细学习了下,下面分享给大家我的学习成果. 二.简单了解并使用 try-with-resources语法比较容易使用,一般随便搜索看下示例代码就能用起来了.JDK 对这个语法的支持是为了更好的管理资源,准确说是资源的释放. 当一个资源类实现了该接口close方法,在使用try-with-resources语法创建的资源抛出异常后,JVM会自动调用close 方法进行资

随机推荐