Java8中的Stream的汇总和分组操作~它并不难的

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

前言
在前面的文章中其实大家也已经看到我使用过collect(Collectors.toList()) 将数据最后汇总成一个 List 集合。
但其实还可以转换成Integer、Map、Set 集合等。
一、查找流中的最大值和最小值
     static List 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 collect1 = students.stream().collect(Collectors.maxBy((s1, s2) -> s1.getAge() - s2.getAge()));
         Optional 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等等
我们暂且就先以summingDouble和summarizingDouble来举例吧
案例数据仍然是上面的那些student数据...

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

1、首先使用summingDouble 和 averagingDouble 来实现
 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>才能存放勒,我在循环的时候,是不是每次都得判断一下学生所在的学校的名称,然后看是否要给它添加到这个List集合中去,最后再put到map中去呢?
看着就特别繁琐,但是在 stream 中就变成了一行代码,其他的东西,都是 Java 内部给你优化了。
         // 我想知道每所学校中,学生的数量及相关信息,只要这一行代码即可
         Map<String, List> 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> 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> 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>> 啦,第一级存放 20岁以上以下两组数据,第二级存放以每个学校名为key的数据信息。
 Map<String, Map<String, List>> 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}
 */
复制代码

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

后记

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

相关文章
|
3月前
|
安全 Java API
告别繁琐编码,拥抱Java 8新特性:Stream API与Optional类助你高效编程,成就卓越开发者!
【8月更文挑战第29天】Java 8为开发者引入了多项新特性,其中Stream API和Optional类尤其值得关注。Stream API对集合操作进行了高级抽象,支持声明式的数据处理,避免了显式循环代码的编写;而Optional类则作为非空值的容器,有效减少了空指针异常的风险。通过几个实战示例,我们展示了如何利用Stream API进行过滤与转换操作,以及如何借助Optional类安全地处理可能为null的数据,从而使代码更加简洁和健壮。
106 0
|
9天前
|
Java API 数据处理
探索Java中的Lambda表达式与Stream API
【10月更文挑战第22天】 在Java编程中,Lambda表达式和Stream API是两个强大的功能,它们极大地简化了代码的编写和提高了开发效率。本文将深入探讨这两个概念的基本用法、优势以及在实际项目中的应用案例,帮助读者更好地理解和运用这些现代Java特性。
|
1月前
|
Java 流计算
Flink-03 Flink Java 3分钟上手 Stream 给 Flink-02 DataStreamSource Socket写一个测试的工具!
Flink-03 Flink Java 3分钟上手 Stream 给 Flink-02 DataStreamSource Socket写一个测试的工具!
35 1
Flink-03 Flink Java 3分钟上手 Stream 给 Flink-02 DataStreamSource Socket写一个测试的工具!
|
1月前
|
Java Shell 流计算
Flink-02 Flink Java 3分钟上手 Stream SingleOutputStreamOpe ExecutionEnvironment DataSet FlatMapFunction
Flink-02 Flink Java 3分钟上手 Stream SingleOutputStreamOpe ExecutionEnvironment DataSet FlatMapFunction
21 1
Flink-02 Flink Java 3分钟上手 Stream SingleOutputStreamOpe ExecutionEnvironment DataSet FlatMapFunction
|
2月前
|
存储 Java API
Java——Stream流详解
Stream流是JDK 8引入的概念,用于高效处理集合或数组数据。其API支持声明式编程,操作分为中间操作和终端操作。中间操作包括过滤、映射、排序等,可链式调用;终端操作则完成数据处理,如遍历、收集等。Stream流简化了集合与数组的操作,提升了代码的简洁性
70 11
Java——Stream流详解
|
1月前
|
存储 Java 数据处理
Flink-01 介绍Flink Java 3分钟上手 HelloWorld 和 Stream ExecutionEnvironment DataSet FlatMapFunction
Flink-01 介绍Flink Java 3分钟上手 HelloWorld 和 Stream ExecutionEnvironment DataSet FlatMapFunction
28 1
|
2月前
|
Java API C++
Java 8 Stream Api 中的 peek 操作
本文介绍了Java中`Stream`的`peek`操作,该操作通过`Consumer&lt;T&gt;`函数消费流中的每个元素,但不改变元素类型。文章详细解释了`Consumer&lt;T&gt;`接口及其使用场景,并通过示例代码展示了`peek`操作的应用。此外,还对比了`peek`与`map`的区别,帮助读者更好地理解这两种操作的不同用途。作者为码农小胖哥,原文发布于稀土掘金。
107 9
Java 8 Stream Api 中的 peek 操作
|
2月前
|
Java C# Swift
Java Stream中peek和map不为人知的秘密
本文通过一个Java Stream中的示例,探讨了`peek`方法在流式处理中的应用及其潜在问题。首先介绍了`peek`的基本定义与使用,并通过代码展示了其如何在流中对每个元素进行操作而不返回结果。接着讨论了`peek`作为中间操作的懒执行特性,强调了如果没有终端操作则不会执行的问题。文章指出,在某些情况下使用`peek`可能比`map`更简洁,但也需注意其懒执行带来的影响。
126 2
Java Stream中peek和map不为人知的秘密
|
2月前
|
Java 大数据 API
Java 流(Stream)、文件(File)和IO的区别
Java中的流(Stream)、文件(File)和输入/输出(I/O)是处理数据的关键概念。`File`类用于基本文件操作,如创建、删除和检查文件;流则提供了数据读写的抽象机制,适用于文件、内存和网络等多种数据源;I/O涵盖更广泛的输入输出操作,包括文件I/O、网络通信等,并支持异常处理和缓冲等功能。实际开发中,这三者常结合使用,以实现高效的数据处理。例如,`File`用于管理文件路径,`Stream`用于读写数据,I/O则处理复杂的输入输出需求。
|
2月前
|
Java Go PHP
Java分组匹配
Java分组匹配
25 5