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

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

接着来谈一谈 Stream 吧,今天说到的是归约和汇总

前文:

Java 8中 Stream 的小知识

Stream 的使用,我觉得使用它是非常方便的~

前言

在前面的文章中其实大家也已经看到我使用过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类专门为汇总提供了一些个工厂方法:

image.png

当然除此之外还有 求平均数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()并不只是一个人,它也有兄弟姐妹

image.png

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

改为:我想知道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}
 */

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

后记

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

目录
相关文章
|
5天前
|
安全 Java 大数据
|
5天前
|
Java Unix Windows
|
2天前
|
自然语言处理 Java API
Java 8的Stream API和Optional类:概念与实战应用
【5月更文挑战第17天】Java 8引入了许多重要的新特性,其中Stream API和Optional类是最引人注目的两个。这些特性不仅简化了集合操作,还提供了更好的方式来处理可能为空的情况,从而提高了代码的健壮性和可读性。
25 7
|
3天前
|
Java API
Java 8新特性之Lambda表达式与Stream API
【5月更文挑战第17天】本文将介绍Java 8中的两个重要特性:Lambda表达式和Stream API。Lambda表达式是一种新的编程语法,它允许我们将函数作为参数传递给其他方法,从而使代码更加简洁。Stream API是一种用于处理集合的新工具,它提供了一种高效且易于使用的方式来处理数据。通过结合使用这两个特性,我们可以编写出更加简洁、高效的Java代码。
8 0
|
5天前
|
安全 Java API
Java Stream API详解与使用
Java Stream API是Java 8引入的特性,提供函数式操作处理集合,支持链式操作和并行处理,提升代码可读性和性能。关键点包括:延迟执行的中间操作(如filter, map)和触发计算的终端操作(如collect, forEach)。示例展示了如何从Person列表过滤出年龄大于20的姓名并排序。使用Stream时注意避免中间操作的副作用,终端操作后Stream不能复用,以及并行操作的线程安全性。
12 1
|
2天前
|
安全 Java API
Java进阶-Java Stream API详解与使用
效、更易于维护的代码,同时享受到函数式编程带来的好处。
10 2
|
2天前
|
Java 大数据 API
利用Java Stream API实现高效数据处理
在大数据和云计算时代,数据处理效率成为了软件开发者必须面对的重要挑战。Java 8及以后版本引入的Stream API为开发者提供了一种声明式、函数式的数据处理方式,极大提升了数据处理的效率和可读性。本文将详细介绍Java Stream API的基本概念、使用方法和性能优势,并通过实际案例展示如何在实际项目中应用Stream API实现高效数据处理。
|
3天前
|
Java
Java8 Stream 用法合集
Java8 Stream 用法合集
|
5天前
|
Java API
Java 8新特性之Lambda表达式与Stream API实践指南
【5月更文挑战第15天】 随着Java语言的不断发展,Java 8作为一个重要的版本,引入了许多令人兴奋的新特性。其中,Lambda表达式和Stream API是Java 8最受关注的两个特性。本文将深入探讨Lambda表达式的基本概念、语法和使用场景,以及如何结合Stream API实现更加简洁、高效的代码编写。通过实例演示,帮助读者快速掌握这两个新特性,提高Java编程能力。
|
5天前
|
Java API 数据处理
Java一分钟之-Stream API:数据处理新方式
【5月更文挑战第13天】Java 8的Stream API为集合操作提供了声明式编程,简化数据处理。本文介绍了Stream的基本概念、常见问题和易错点。问题包括并行流与顺序流的区别,状态改变操作的影响,以及忘记调用终止操作和误用`peek()`。理解并合理使用Stream API能提升代码效率和可维护性。实践中不断探索,将发掘更多Stream API的潜力。
12 3