前言
1、什么是Stream
前面我们讲了
Lambda
表达式与Optional
类,下面我们将会使用这两个新特性,特别是Lambda
。
Stream 是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列集合讲的是数据,Stream讲的是计算!
注意:
- Stream自己不会存储元素
- Stream不会改变源对象。相反,他们会返回一个特有结果的新Stream
- Stream操作是延迟执行的。这意味着他们会等到需要结果的时候才执行
2、为什么要使用StreamAPI
实际开发中,项目多数数据源都来自于
Mysql,Oracle等
。但现在数据源可以更多了,有MongDB,Radis等,而这些NoSQL的数据就需要Java层面去处理Stream和Collection集合的区别:==Collectioin是一种静态的内存数据结构,而Stream是有关计算的。==前者主要面向内存,存储在内存中,后者主要是面向CPU,通过CPU实现计算。
2.1、举例
假设你有一个班所有学生的分数数据,你需要从中筛选出成绩不及格的,并且按照分数排序,打印按顺序学生的名称
JDK1.7写法
package com.tcc.test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
/**
* @author 宇辰
* @date 2022/8/31-8:53
**/
public class Test{
public static void main(String[] args){
// 元数据
List<Student> students = Arrays.asList(
new Student("张三", 20, "100班", 56D),
new Student("李四", 23, "101班", 44D),
new Student("王五", 25, "102班", 44.5D)
);
ArrayList<Student> list = new ArrayList<>();
// 筛选出所有成绩不合格的学生
for (Student s : students) {
if (s.getScore() < 60){
list.add(s);
}
}
// 排序
Comparator com = new Comparator() {
@Override
public int compare(Object o1, Object o2) {
Student stu1 = (Student) o1;
Student stu2 = (Student) o2;
// 按照分数排序
return stu1.getScore().compareTo(stu2.getScore());
}
};
list.sort(com);
// 遍历得出所有名称
/*
李四
王五
张三
*/
for (Student s : list) {
System.out.println(s.getName());
}
}
}
class Student{
// 姓名
private String name;
// 年龄
private Integer age;
// 班级
private String clazz;
// 分数
private Double score;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getClazz() {
return clazz;
}
public void setClazz(String clazz) {
this.clazz = clazz;
}
public Double getScore() {
return score;
}
public void setScore(Double score) {
this.score = score;
}
public Student() {
}
public Student(String name, Integer age, String clazz, Double score) {
this.name = name;
this.age = age;
this.clazz = clazz;
this.score = score;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", clazz='" + clazz + '\'' +
", score=" + score +
'}';
}
}
JDK1.8写法
public class Test{
public static void main(String[] args){
// 元数据
List<Student> students = Arrays.asList(
new Student("张三", 20, "100班", 56D),
new Student("李四", 23, "101班", 44D),
new Student("王五", 25, "102班", 44.5D)
);
/*
李四
王五
张三
*/
students.stream()
// 中间操作,过滤小于60分的同学
.filter(s -> s.getScore() < 60)
// 中间操作,定制排序
.sorted((s1,s2) -> Double.compare(s1.getScore(),s2.getScore()))
// 中间操作,依次获取学生的姓名
.map(s -> s.getName())
// 终止操作,内部迭代打印获取到的每个学生的姓名
.forEach(
System.out::println
);
}
}
....省略Student类
2.2、执行步骤
3、StreamAPI说明
- Java8中有两大最为重要的改变。第一个是Lambda表达式;另外一个是Stream API
- ==Stream API(java.util.stream)==把真正的函数式编程风格引入到了Java中。这是目前为止对Java类库最好的补充,因为StreamAPI可以极大提供Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。
- Stream是Java8中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非重复杂的查找、过滤和映射数据等操作。使用==Stream API对集合数据进行操作,就类似于使用SQL执行的数据库查询。==也可以使用Stream API来并行执行操作。简言之,Stream API提供了一种高效且易于使用的处理数据的方式
4、Stream的操作三个步骤
1、创建Stream
一个数据源(如:集合、数组),获取一个流
2、中间操作
一个中间操作链,对数据源的数据进行处理
3、终止操作(终端操作)
一旦执行终止操作,就执行中间操作链,并产生结果:之后,不会再被使用
5、并行流与串行流
并行流:就是把一个内容分成多个数据块,并用不用的线程分别处理每个数据块的。相比较串行的流,并行的流可以很大程度上提高程序的执行效率。
Java 8 中将并行进行了优化,我们可以很容易的对数据进行并行操作。
StreamAPI可以声明性地通过
parallel()
与sequential()
在并行流与顺序流之间进行切换
创建Stream流方式
以下所有演示代码都使用了Lambda表达式,如果不了解的话,可以阅读:Lamdba表达式快速学习与使用
1、通过集合
Java8 中的Collection接口被扩展,提供了两个获取流的方法:
- default Stream stream():返回一个顺序流
- default Stream parallelStream():返回一个并行流
public static void main(String[] args){
ArrayList<String> list = new ArrayList<>();
// 创建一个串行流
Stream<String> stream = list.stream();
// 创建一个并行流
Stream<String> parallelStream = list.parallelStream();
}
2、通过数组
Java8 中的Arrays 的静态方法stream()可以获取数组流:
- seatic Stream stream(T[] array):返回一个流
重载形式,能够处理对应基本类型的数组:
- public static IntStream stream(int[] array)
- public static LongStream stream(logn[] array)
- public static DoubleStream stream(double[] array)
public static void main(String[] args){
String[] s = new String[16];
// 串行流,没有提供创建并行流的方法
Stream<String> str1 = Arrays.stream(s);
}
3、通过Stream的of()
可以调用Stream类静态方法of(),通过显示值创建一个流。它可以接收任意数量的参数
- public static Stream of(T… values):返回一个流
public static void main(String[] args){
Stream<Integer> stream = Stream.of(123, 222, 344);
}
4、由文件生产流
Java中用于处理文件等I/O操作的NIO API(非阻塞I/O)已更新,以便利用Stream API。java.nio.file.Files中的很多静态方法都会返回一个流。例如,一个很有用的方法:
Files.lines
,它会返回一个由指定文件中的各行构成的字符串流。例子:差异一个文件中有多少各不相同的词
public static void main(String[] args){
// 这样写会自动关闭流
try (
Stream<String> lines = Files.lines(Paths.get("data.txt"), Charset.defaultCharset());
){
// 需要用flatMap方法扁平化返回值
lines.flatMap(line -> Arrays.stream(line.split(" ")))
// 中间操作,去重
.distinct()
// 终止操作,计数
.count();
}catch (IOException e){
e.printStackTrace();
}
}
5、创建无限流
可以使用静态方法Stream.iterate()和Stream.generate(),创建无限流。
- 迭代
- public static Stream iterate(final T seed,final UnaryOperator f)
- 生成
- public static Stream generate(Supplier s)
5.1、iterate
public static void main(String[] args){
Stream<Integer> iterate = Stream.iterate(0, t -> t + 2);
// 中间操作,后面会讲到,用于截断流,取前三个
Stream<Integer> limit = iterate.limit(3);
/*
0
2
4
*/
// 终止操作 内部迭代
limit.forEach(System.out::println);
}
5.2、generate
public static void main(String[] args){
Stream<Double> generate = Stream.generate(Math::random);
Stream<Double> limit = generate.limit(3);
/*
0.49171014974935257
0.7073185115880065
0.5253605859404055
*/
limit.forEach(System.out::println);
}
中间操作
多个
中间操作
可以连接起来形成一个流水线,除非流水线
上触发终止操作,否则中间操作不会执行任何的处理!而在终止操作时一次性全部处理,成为"惰性求值"
1、筛选与切片
- filter:接收Lambda,从流汇总排除某些元素
- distinct():筛选,通过流所生成元素的
hashCode()和equals()
去除重复元素- limit(long maxSize):截断流,使其元素不超过给定数量
- skip(long n):跳过元素,返回一个扔掉了前n个元素的流。若流中元素不足n个,则返回一个空流。与limit(n)互补
1.1、filter
该操作会接受一个谓词(一个返回boolean的函数)作为参数,并返回一个包括所有符合谓词的元素的流
比如说:你想获取所有成绩不及格的学生
public static void main(String[] args){
// 元数据
List<Student> students = Arrays.asList(
new Student("张三", 20, "100班", 88D),
new Student("李四", 21, "101班", 50D),
new Student("王五", 22, "102班", 44.5D),
new Student("赵六", 20, "103班", 44.5D),
new Student("田七", 21, "103班", 91D)
);
students.stream()
// 如果满足条件,就继续执行。不满足条件的剔除
.filter(s -> s.getScore() < 60)
/*
Student{name='李四', age=21, clazz='101班', score=50.0}
Student{name='王五', age=22, clazz='102班', score=44.5}
Student{name='赵六', age=20, clazz='103班', score=44.5}
*/
.forEach(
System.out::println
);
}
// ...Student类定义省略,前言第二部分举例里面有。。
1.2、distinct
去重,它会返回一个元素各异(根据流所生成元素的hashCode和equals方法实现)的流
比如说:你想知道同学的年龄都在哪个年龄段上有
public static void main(String[] args){
// 元数据
List<Student> students = Arrays.asList(
new Student("张三", 20, "100班", 88D),
new Student("李四", 21, "101班", 50D),
new Student("王五", 22, "102班", 44.5D),
new Student("赵六", 20, "103班", 44.5D),
new Student("田七", 21, "103班", 91D)
);
students.stream()
// 获取所有同学的年龄
.map(Student::getAge)
// 去重
.distinct()
/*
20
21
22
*/
.forEach(
System.out::println
);
}
// ...Student类定义省略,前言第二部分举例里面有。。
1.3、limit
截短流,该方法会返回一个不超过给定长度的流。所需的长度作为参数传递给limit。
如果流式有序的,则最多会返回前n个元素。
比如说:你想获取成绩不及格的前两个人
public static void main(String[] args){
// 元数据
List<Student> students = Arrays.asList(
new Student("张三", 20, "100班", 88D),
new Student("李四", 21, "101班", 50D),
new Student("王五", 22, "102班", 44.5D),
new Student("赵六", 20, "103班", 44.5D),
new Student("田七", 21, "103班", 91D)
);
students.stream()
// 如果满足条件,就继续执行。不满足条件的剔除
.filter(s -> s.getScore() < 60)
.limit(2)
/*
Student{name='李四', age=21, clazz='101班', score=50.0}
Student{name='王五', age=22, clazz='102班', score=44.5}
*/
.forEach(
System.out::println
);
}
// ...Student类定义省略,前言第二部分举例里面有。。
1.4、skip
流还支持skip(n)方法,返回一个扔掉了前n个元素的流。如果流中元素不足n个,则返回一个空流。
注意:limit(n)和skip(n)是互补的:
比如说:你想获取除了前两个后面的所有不及格的学生
public static void main(String[] args){
// 元数据
List<Student> students = Arrays.asList(
new Student("张三", 20, "100班", 88D),
new Student("李四", 21, "101班", 50D),
new Student("王五", 22, "102班", 44.5D),
new Student("赵六", 20, "103班", 44.5D),
new Student("田七", 21, "103班", 91D)
);
students.stream()
// 如果满足条件,就继续执行。不满足条件的剔除
.filter(s -> s.getScore() < 60)
.skip(2)
// Student{name='赵六', age=20, clazz='103班', score=44.5}
.forEach(
System.out::println
);
}
2、映射
- map(Function f):接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素
- flatMap(Function f):接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流
- mapToDouble(ToDoubleFunction f):接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的DoubleStream
- mapToInt(ToIntFunction f):接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的IntStream
- mapToLong(ToLongFunction f):接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的LongStream
2.1、map
流支持map方法,它会接受一个函数作为参数。这个函数会被应用到每个元素上,并将其映射成一个新的元素(使用映射一次,是因为它和转换类似,但其中的细微差别在于,它是"创建一个新的版本",而不是去"修改"原来的数据)
比如说:你想获取所有学生的姓名
public static void main(String[] args){
// 元数据
List<Student> students = Arrays.asList(
new Student("张三", 20, "100班", 88D),
new Student("李四", 21, "101班", 50D),
new Student("王五", 22, "102班", 44.5D),
new Student("赵六", 20, "103班", 44.5D),
new Student("田七", 21, "103班", 91D)
);
students.stream()
.map(Student::getName)
/*
李四
王五
赵六
田七
*/
.forEach(System.out::println);
}
比如说:你想再获取每个人名字有多长
public static void main(String[] args){
// 元数据
List<Student> students = Arrays.asList(
new Student("张三", 20, "100班", 88D),
new Student("李四", 21, "101班", 50D),
new Student("王五", 22, "102班", 44.5D),
new Student("赵六", 20, "103班", 44.5D),
new Student("田七", 21, "103班", 91D)
);
students.stream()
.map(Student::getName)
.map(String::length)
/*
2
2
2
2
*/
.forEach(System.out::println);
}
2.2、flatMap
flatMap方法让你把一个流中的每个值都换成另一个流,然后把所有流链接起来成为一个流
比如说:你想获取所有人名字不相同的字有哪些,我们先来使用map看看会有什么问题
2.1、使用map
public static void main(String[] args){
// 元数据
List<Student> students = Arrays.asList(
new Student("张三", 20, "100班", 88D),
new Student("张四", 21, "101班", 50D),
new Student("李三", 20, "103班", 44.5D)
);
// 你会发现它返回的不是你想要的Stream<String>,而是里面包含了一层String[]
Stream<String[]> stream = students.stream()
.map(Student::getName)
.map(str -> str.split(""))
.distinct();
// 这时候你需要在forEach里面再遍历strings数组,并没有达到去重的效果
/*
张
三
张
四
李
三
*/
stream.forEach(
strings -> {
for (String s : strings) {
System.out.println(s);
}
}
);
}
2.2、原因
2.3、使用flatMap
我们可以使用flatMap来简化上面代码
- 我们使用split方法获取到的是一个数组,我们上面学习到,可以使用
Arrays.stream()
方法,把数组转换为一个Stream流- 转换为流后,我们的返回值就会为
Stream<Stream<String>>
,这时候我们使用flatMap方法,把这个流中流,转换为单个流Stream<String>
实践如下
public static void main(String[] args){
// 元数据
List<Student> students = Arrays.asList(
new Student("张三", 20, "100班", 88D),
new Student("张四", 21, "101班", 50D),
new Student("李三", 20, "103班", 44.5D)
);
students.stream()
.map(Student::getName)
.flatMap(str -> Arrays.stream(str.split("")))
.distinct()
/*
张
三
四
李
*/
.forEach(System.out::println);
}
2.4、执行流程如下
2.3、数值流
想IntSteam、DoubleStream、LongStream都属于数值流,我们会放到终止操作的第二章(归约)后面详细讲解
3、排序
- sorted():产生一个新流,其中按自然顺序排序
- sorted(Comparator com):产生一个新流,其中按比较器顺序排序
3.1、sorted()
根据元素类型内置的排序规则进行排序,默认升序排序
比如说:根据每个学生的分数进行排序
public static void main(String[] args){
// 元数据
List<Student> students = Arrays.asList(
new Student("张三", 20, "100班", 88D),
new Student("李四", 21, "101班", 50D),
new Student("王五", 22, "102班", 44.5D),
new Student("赵六", 20, "103班", 44.5D),
new Student("田七", 21, "103班", 91D)
);
students.stream()
.map(Student::getScore)
// 根据Double类内置的compareTo方法进行排序
.sorted()
/*
44.5
44.5
50.0
88.0
91.0
*/
.forEach(System.out::println);
}
3.2、sorted(Comparator com)
根据自定义排序规则进行排序
比如说:根据每个学生分数进行排序,如果分数相同,则根据年龄排序
// 元数据
List<Student> students = Arrays.asList(
new Student("张三", 20, "100班", 88D),
new Student("李四", 21, "101班", 50D),
new Student("王五", 22, "102班", 44.5D),
new Student("赵六", 20, "103班", 44.5D),
new Student("田七", 21, "103班", 91D)
);
students.stream()
.sorted((s1,s2) -> {
int i = s1.getScore().compareTo(s2.getScore());
if(i == 0){
// 如果需要降序排序,前面加个 - 号即可
return s1.getAge().compareTo(s2.getAge());
}
// 如果需要降序排序,前面加个 - 号即可
return i;
})
/*
Student{name='赵六', age=20, clazz='103班', score=44.5}
Student{name='王五', age=22, clazz='102班', score=44.5}
Student{name='李四', age=21, clazz='101班', score=50.0}
Student{name='张三', age=20, clazz='100班', score=88.0}
Student{name='田七', age=21, clazz='103班', score=91.0}
*/
.forEach(System.out::println);
}
终止操作
终端操作会从流的流水线生成结果。其结果可以是任何不是流的值,例如:List、Integer,甚至是void。
流进行了终止操作后,不能再次使用。可以再次生成一个新流进行使用
1、匹配与查找
- allMatch(Predicate p):检查是否匹配所有元素
- anyMatch(Predicate p):检查是否至少匹配一个元素
- noneMatch(Predicate p):检查是否没有匹配所有元素
- findFirst():返回第一个元素
- findAny():返回当前流中的任意元素
- count():返回流中元素总数
- max(Comparator c):返回流中最大值
- min(Comparator c):返回流中最小值
- forEach(Consumer c):内部迭代(使用Collection接口需要用户去做迭代,成为
外部迭代
。相反,StreamAPI使用内部迭代———它帮你把迭代做了)
1.1、allMatch
检测流中所有元素是否都能匹配给定的谓词
比如说:你想查看是否所有学生成绩都及格了
public static void main(String[] args){
// 元数据
List<Student> students = Arrays.asList(
new Student("张三", 20, "100班", 88D),
new Student("李四", 21, "101班", 50D),
new Student("王五", 22, "102班", 44.5D),
new Student("赵六", 20, "103班", 44.5D),
new Student("田七", 21, "103班", 91D)
);
boolean b = students.stream()
.allMatch(s -> s.getScore() >= 60);
System.out.println(b); // false
}
1.2、anyMatch
检测流中是否有一个元素能匹配给定的谓词
比如说:你想查看是否有一个同学不及格
public static void main(String[] args){
// 元数据
List<Student> students = Arrays.asList(
new Student("张三", 20, "100班", 88D),
new Student("李四", 21, "101班", 50D),
new Student("王五", 22, "102班", 44.5D),
new Student("赵六", 20, "103班", 44.5D),
new Student("田七", 21, "103班", 91D)
);
boolean b = students.stream()
.anyMatch(s -> s.getScore() < 60);
System.out.println(b); // true
}
1.3、noneMatch
与
allMatch
相反,检测流中所有元素是否都不能匹配给定的谓词比如说:你想看看是否所有的学生都不是105班的
public static void main(String[] args){
// 元数据
List<Student> students = Arrays.asList(
new Student("张三", 20, "100班", 88D),
new Student("李四", 21, "101班", 50D),
new Student("王五", 22, "102班", 44.5D),
new Student("赵六", 20, "103班", 44.5D),
new Student("田七", 21, "103班", 91D)
);
boolean b = students.stream()
.noneMatch(s -> "105".equals(s.getClazz()));
System.out.println(b); // true
}
1.4、findFirst
有些流使用排序来指定集合中的顺序,对于这种元素,你可能想要找到第一个元素。
比如说:找到分数第一的学生
public static void main(String[] args){
// 元数据
List<Student> students = Arrays.asList(
new Student("张三", 20, "100班", 88D),
new Student("李四", 21, "101班", 50D),
new Student("王五", 22, "102班", 44.5D),
new Student("赵六", 20, "103班", 44.5D),
new Student("田七", 21, "103班", 91D)
);
// 使用Optional可以防止找不到,空指针异常
Optional<Student> student = students.parallelStream()
// 使用Comparator的默认方法,reversed方法用于反转顺序,默认升序排序,需要降序排序,然后拿到第一名的学生
.sorted(Comparator.comparingDouble(Student::getScore).reversed())
.findFirst();
System.out.println(student); // Optional[Student{name='田七', age=21, clazz='103班', score=91.0}]
}
1.5、findAny
findAny方法将返回当前流中任意元素。可以与其他流操作结合使用
比如说:你可能想抽取103班的任意一人
public static void main(String[] args){
// 元数据
List<Student> students = Arrays.asList(
new Student("张三", 20, "100班", 88D),
new Student("李四", 21, "101班", 50D),
new Student("王五", 22, "102班", 44.5D),
new Student("赵六", 20, "103班", 44.5D),
new Student("田七", 21, "103班", 91D)
);
// 使用Optional可以防止找不到,空指针异常
// 使用并行流可以直观看出(多运行几次,查看结果)
Optional<Student> student = students.parallelStream()
.filter(s -> "103班".equals(s.getClazz()))
.findAny();
System.out.println(student); // Optional[Student{name='赵六', age=20, clazz='103班', score=44.5}]
}
1.6、count
比如说:你想知道有几个学生不及格
public static void main(String[] args){
// 元数据
List<Student> students = Arrays.asList(
new Student("张三", 20, "100班", 88D),
new Student("李四", 21, "101班", 50D),
new Student("王五", 22, "102班", 44.5D),
new Student("赵六", 20, "103班", 44.5D),
new Student("田七", 21, "103班", 91D)
);
long count = students.stream()
.filter(s -> s.getScore() < 60)
.count();
System.out.println(count); // 3
}
1.7、max
比如说:你想知道最高分是多少
public static void main(String[] args){
// 元数据
List<Student> students = Arrays.asList(
new Student("张三", 20, "100班", 88D),
new Student("李四", 21, "101班", 50D),
new Student("王五", 22, "102班", 44.5D),
new Student("赵六", 20, "103班", 44.5D),
new Student("田七", 21, "103班", 91D)
);
// 从1-100的所有偶数个数
Optional<Double> max = students.stream()
.map(Student::getScore)
.max(Double::compare);
System.out.println(max); // Optional[91.0]
}
1.8、min
比如说:你想知道最低分是多少
public static void main(String[] args){
// 元数据
List<Student> students = Arrays.asList(
new Student("张三", 20, "100班", 88D),
new Student("李四", 21, "101班", 50D),
new Student("王五", 22, "102班", 44.5D),
new Student("赵六", 20, "103班", 44.5D),
new Student("田七", 21, "103班", 91D)
);
// 从1-100的所有偶数个数
Optional<Double> max = students.stream()
.map(Student::getScore)
.min(Double::compare);
System.out.println(max); // Optional[44.5]
}
1.9、forEach
比如说:你想打印成绩不及格的学生姓名
public static void main(String[] args){
// 元数据
List<Student> students = Arrays.asList(
new Student("张三", 20, "100班", 88D),
new Student("李四", 21, "101班", 50D),
new Student("王五", 22, "102班", 44.5D),
new Student("赵六", 20, "103班", 44.5D),
new Student("田七", 21, "103班", 91D)
);
students.stream()
.filter(s -> s.getScore() < 60)
/*
李四
王五
赵六
*/
.forEach(s -> System.out.println(s.getName()));
}
2、归约
- reduce(T iden, BinaryOperator b):可以将流中元素反复结合起来,得到一个值。返回T
- 参数一:变量的初始值
- 参数二:将列表中所有元素结合在一起的操作
- reduce(BinaryOperator b):可以将流中元素反复结合起来,的达到一个值。返回Optional
map和reduce的连接通常称为map-reduce模式,因Google用它来进行网络搜索而出名
2.1、reduce(T iden, BinaryOperator b)
总和:比如说:你想知到所有学生的总分是多少
public static void main(String[] args){
// 元数据
List<Student> students = Arrays.asList(
new Student("张三", 20, "100班", 88D),
new Student("李四", 21, "101班", 50D),
new Student("王五", 22, "102班", 44.5D),
new Student("赵六", 20, "103班", 44.5D),
new Student("田七", 21, "103班", 91D)
);
// 使用Optional可以防止找不到,空指针异常
Double reduce = students.stream()
// 获取所有同学的分数
.map(Student::getScore)
// .reduce(0D, (a, b) -> a + b);
// 优化,Double有一个sum的静态方法,恰巧可以让我们使用方法引用
.reduce(0D,Double::sum);
System.out.println(reduce); // 318.0
}
最大值:比如说:你想知道最高分是多少
public static void main(String[] args){
// 元数据
List<Student> students = Arrays.asList(
new Student("张三", 20, "100班", 88D),
new Student("李四", 21, "101班", 50D),
new Student("王五", 22, "102班", 44.5D),
new Student("赵六", 20, "103班", 44.5D),
new Student("田七", 21, "103班", 91D)
);
// 使用Optional可以防止找不到,空指针异常
Optional<Double> reduce = students.stream()
.map(Student::getScore)
// .reduce((a, b) -> a>b?a:b);
// 优化,Double有一个max的静态方法,恰巧可以让我们使用方法引用
.reduce(Double::max);
System.out.println(reduce); // Optional[91.0]
}
最小值:比如说:你想知道最低分是多少
public static void main(String[] args){
// 元数据
List<Student> students = Arrays.asList(
new Student("张三", 20, "100班", 88D),
new Student("李四", 21, "101班", 50D),
new Student("王五", 22, "102班", 44.5D),
new Student("赵六", 20, "103班", 44.5D),
new Student("田七", 21, "103班", 91D)
);
// 使用Optional可以防止找不到,空指针异常
Optional<Double> reduce = students.stream()
.map(Student::getScore)
// .reduce((a, b) -> a>b?b:a);
// 优化,Double有一个min的静态方法,恰巧可以让我们使用方法引用
.reduce(Double::min);
System.out.println(reduce); // Optional[44.5]
}
2.2、reduce(BinaryOperator b)
为什么这个方法的返回值会是Optional类型的呢,考虑到流中没有任何元素的情况,reduce无法返回其和,因为它没有初始值
比如说:你想知到所有学生的总分是多少
public static void main(String[] args){
// 元数据
List<Student> students = Arrays.asList(
new Student("张三", 20, "100班", 88D),
new Student("李四", 21, "101班", 50D),
new Student("王五", 22, "102班", 44.5D),
new Student("赵六", 20, "103班", 44.5D),
new Student("田七", 21, "103班", 91D)
);
// 使用Optional可以防止找不到,空指针异常
Optional<Double> reduce = students.stream()
.map(Student::getScore)
// .reduce((a, b) -> a + b);
// 优化,Double有一个sum的静态方法,恰巧可以让我们使用方法引用
.reduce(Double::sum);
System.out.println(reduce); // Optional[318.0]
}
3、map补充-数值流
经过上面的归约计算求和,我们回想,如果直接有一个sum()的终端操作,不是更好,就不需要调用Double里面的方法了。
但这是不可能的。问题在于map方法会生成一个Stream。虽然流中的元素是Double类型,但Stream接口没有定义sum方法。
为什么没有呢?比方说你只有一个像Student那样的Stream,把每个学生加起来是没有任何意义的。
但是不要担心,Stream API还提过了原始类型流特化,专门支持处理数值流的方法
原始类型流特化:
Java 8引入了三个原始类型流特化接口来解决这个问题,从而不面了暗含的装箱成本。每个接口都带来了进行常用数值归约的新方法,比如对数值流求和的sum,找到最大元素的max等…
- 映射到数值流
- mapToInt:将
Stream<Integer>
类型转化为IntStream
类型,执行专属的操作- mapToDouble:将
Stream<Double>
类型转化为DoubleStream
类型,执行专属的操作- mapToLong:将
Stream<Long>
类型转化为LongStream
类型,执行专属的操作- 转换回对象流:一旦有了数值流,你可能会想把它转换回非特化流
- boxed:将原始流转换为一般流
3.1、mapToDouble
这里只讲解这一个,因为其他两个使用方法和这个一模一样,只是操作的数据类型不一致而已
比如说:在这里把上面使用
reduce
进行的求和、最大值、最小值再次优化一遍,并且再获取所有学生的平均分
- 这里,你会看到
OptionalDouble
类,这是对于Optional
原始类型的特化版本,有OptionalInt、OptionalDouble、OptionalLong
三个。因为求和的例子很容易,因为它有一个默认值:0.但是,如果要计算IntStream中的最大元素,就得换个贩子了,因为0是错误的结果。
public static void main(String[] args){
// 元数据
List<Student> students = Arrays.asList(
new Student("张三", 20, "100班", 88D),
new Student("李四", 21, "101班", 50D),
new Student("王五", 22, "102班", 44.5D),
new Student("赵六", 20, "103班", 44.5D),
new Student("田七", 21, "103班", 91D)
);
// 分数总和
DoubleStream doubleStream = students.stream()
.mapToDouble(Student::getScore);
double sum = doubleStream.sum();
System.out.println(sum); // 318.0
// 最高分
OptionalDouble max = students.stream()
.mapToDouble(Student::getScore)
.max();
System.out.println(max); // OptionalDouble[91.0]
// 最低分
OptionalDouble min = students.stream()
.mapToDouble(Student::getScore)
.min();
System.out.println(min); // OptionalDouble[44.5]
// 平均分
OptionalDouble average = students.stream()
.mapToDouble(Student::getScore)
.average();
System.out.println(average); // OptionalDouble[63.6]
}
3.2、boxed
// 分数总和
DoubleStream doubleStream = students.stream()
.mapToDouble(Student::getScore);
Stream<Double> boxed = doubleStream.boxed();
3.3、数值范围
和数字打交道时,有一个常用的东西就是数值范围。比如,假设你想要生成1和100之间的所有数字。Java8引入了两个可以用于IntStream和LongStream的静态方法,帮助生成这种范围:range和rangeClosed。这两个方法都是第一个参数接收起始值,第二个参数接受结束值。但range是不包含结束值的,而rangeClosed则包含结束值。
比如说:你想知道1-100的所有偶数的个数
// 从1-100的所有偶数个数 如果是range则49个
long count = IntStream.rangeClosed(1, 100)
.filter(i -> i % 2 == 0)
.count();
System.out.println(count); // 50
4、收集
- collect(Collector c):将流转换为其他形式。接收一个Collector接口的实现,用于给Stream中元素做汇总的方法
Collector接口中方法的实现决定了如何对流执行收集的操作(如收集到List、Set、Map)
另外,Collectors实用类提供了很多静态方法,可以方便地创建常见收集器实例,具体方法如下:
- List toList:把流中元素收集到List
- Set toSet:把流中元素收集到Set
- Collection toCollection:把流中元素收集到创建的集合
- Long counting:计算流中的元素的个数
- Integer/Long/Double summingInt/summingLong/summingDouble:对流中元素的整数属性求和
- Double:averagingInt/averagingLong/averagingDouble:计算流中元素Integer属性的平均值
- xxxSummaryStatistics summarizingInt/summarizingLong/summarizingDouble:收集流中Integer属性的统计值。如:平均值
- String joining:连接流中每个字符串
- Optional maxBy:根据比较器选择最大值
- Optional minBy:根据比较器选择最小值
- 归约产生的类型 reducing:从一个作为累加器的初始值开始,利用BinaryOperator与流中元素逐个结合,从而归约成单个值
- 转换函数返回的类型 collectingAndThen:包裹另一个收集器,对其结果转换函数
- Map<K,List> groupingBy:根据某属性值对流分组,属性为K,结果为V
- Map<Bollean,List> partitioningBy:根据true或false进行分区
4.1、toList
将流中所有元素都放进List里面,并返回这个List
比如说:你想获取所有的学生姓名,并放进List里面
public static void main(String[] args){
// 元数据
List<Student> students = Arrays.asList(
new Student("张三", 20, "100班", 88D),
new Student("李四", 21, "101班", 50D),
new Student("王五", 22, "102班", 44.5D),
new Student("赵六", 20, "103班", 44.5D),
new Student("田七", 21, "103班", 91D)
);
// 1.7及之前写法
ArrayList<String> stuName = new ArrayList<>();
for (Student student : students) {
stuName.add(student.getName());
}
System.out.println(stuName); // 张三、李四、王五、赵六、田七
// 1.8写法
List<String> stuNames = students.stream()
.map(Student::getName)
.collect(Collectors.toList());
System.out.println(stuNames); // 张三、李四、王五、赵六、田七
}
4.2、toSet
将流中所有元素都放进Set里面,并返回这个Set集合。使用这个集合也可以进行去重操作
比如说:你想获取所有学生的年龄,并且去重(上面写过相同的案例,使用distinct),最后放到Set集合里
public static void main(String[] args){
// 元数据
List<Student> students = Arrays.asList(
new Student("张三", 20, "100班", 88D),
new Student("李四", 21, "101班", 50D),
new Student("王五", 22, "102班", 44.5D),
new Student("赵六", 20, "103班", 44.5D),
new Student("田七", 21, "103班", 91D)
);
// 1.7及之前写法
HashSet<Integer> stuAge = new HashSet<>();
for (Student student : students) {
stuName.add(student.getAge());
}
System.out.println(stuAge); // 20、21、22
// 1.8写法
Set<Integer> stuAges = students.stream()
.map(Student::getAge)
.collect(Collectors.toSet());
System.out.println(stuAges); // 20、21、22
}
4.3、toCollection
参数为:
Supplier<C> collectionFactory
类型依旧是上面的需求,只不过需要放到LinkedList里面
public static void main(String[] args){
// 元数据
List<Student> students = Arrays.asList(
new Student("张三", 20, "100班", 88D),
new Student("李四", 21, "101班", 50D),
new Student("王五", 22, "102班", 44.5D),
new Student("赵六", 20, "103班", 44.5D),
new Student("田七", 21, "103班", 91D)
);
// 1.7及之前写法
HashSet<Integer> stuAge = new HashSet<>();
for (Student student : students) {
stuAge.add(student.getAge());
}
LinkedList<Integer> stuAge2 = new LinkedList<>();
for (Integer integer : stuAge) {
stuAge2.add(integer);
}
System.out.println(stuAge2); // 20、21、22
// 1.8写法
LinkedList<Integer> stuAges = students.stream()
.map(Student::getAge)
.distinct()
.collect(Collectors.toCollection(LinkedList::new));
System.out.println(stuAges); // 20、21、22
}
4.4、counting
对流中元素进行计数(与上面讲解的
count终端操作
功能一样)比如你需要统计不及格的总共有多少个学生
public static void main(String[] args){
// 元数据
List<Student> students = Arrays.asList(
new Student("张三", 20, "100班", 88D),
new Student("李四", 21, "101班", 50D),
new Student("王五", 22, "102班", 44.5D),
new Student("赵六", 20, "103班", 44.5D),
new Student("田七", 21, "103班", 91D)
);
// 1.7及之前写法
int num = 0;
for (Student student : students) {
if (student.getScore() < 60){
num ++;
}
}
System.out.println(num); // 3
// 1.8写法
Long collects = students.stream()
.filter(s -> s.getScore() < 60)
.collect(Collectors.counting());
System.out.println(collects); // 3
}
4.5、summingInt/summingLong/summingDouble
参数为:
ToIntFunction<? super T>
类型,对流中所有Integer类型的数据求和对数值类型操作,还可以使用上方讲解的特化流进行操作。
比如说:你想获取所有学生的年龄之和
public static void main(String[] args){
// 元数据
List<Student> students = Arrays.asList(
new Student("张三", 20, "100班", 88D),
new Student("李四", 21, "101班", 50D),
new Student("王五", 22, "102班", 44.5D),
new Student("赵六", 20, "103班", 44.5D),
new Student("田七", 21, "103班", 91D)
);
// 1.7及之前写法
Integer num = 0;
for (Student student : students) {
num += student.getAge();
}
System.out.println(num); // 104
// 1.8写法
Integer collects = students.stream()
.collect(Collectors.summingInt(Student::getAge));
System.out.println(collects); // 104
}
4.6、averagingInt/averagingLong/averagingDouble
对Integer类型的数据求平均值
比如说:你想知道所有学生年龄和分数的平均值
public static void main(String[] args){
// 元数据
List<Student> students = Arrays.asList(
new Student("张三", 20, "100班", 88D),
new Student("李四", 21, "101班", 50D),
new Student("王五", 22, "102班", 44.5D),
new Student("赵六", 20, "103班", 44.5D),
new Student("田七", 21, "103班", 91D)
);
// 1.7及之前写法
Double num = 0D;
int count = 0;
int anum = 0;
for (Student student : students) {
count ++;
num += student.getScore();
anum += student.getAge();
}
// 分数
double avg = num / count;
System.out.println(avg); // 63.6
// 年龄 int / int类型,返回值为int类型的话,会丢失精度
Double age = Double.valueOf(anum / count);
System.out.println(age); // 20.8
// 1.8写法
// 分数
Double avgs = students.stream()
.collect(Collectors.averagingDouble(Student::getScore));
System.out.println(avgs); // 104
// 年龄
Double ages = students.stream()
.collect(Collectors.averagingInt(Student::getAge));
System.out.println(ages); // 20.8
}
4.7、summarizingInt/summarizingLong/summarizingDouble
统计Integer类型的所有统计值,比如:平均值、求和、最大值、最小值、计数,然后把所有结果放到``类型里面
比如说,你想知道这次考试,共有多少名学生参加了、最高分、最低分、所有学生的分均分,所有学生的总分为多少,这时候,按照上面的写法,你就需要为每个结果创建一个流,然后调用Collectors里面的不同的计算方法。Java为我们提供了一个总的计算方法,可以计算你所有需求的统计
public static void main(String[] args){
// 元数据
List<Student> students = Arrays.asList(
new Student("张三", 20, "100班", 88D),
new Student("李四", 21, "101班", 50D),
new Student("王五", 22, "102班", 44.5D),
new Student("赵六", 20, "103班", 44.5D),
new Student("田七", 21, "103班", 91D)
);
// 1.7及之前写法,这里就不写了,太多了。。。
// 1.8写法
DoubleSummaryStatistics collect = students.stream()
.collect(Collectors.summarizingDouble(Student::getScore));
// DoubleSummaryStatistics{count=5, sum=318.000000, min=44.500000, average=63.600000, max=91.000000}
System.out.println(collect);
// 最高分
System.out.println(collect.getMax()); // 91.0
// 最低分
System.out.println(collect.getMin()); // 44.5
// 平均值
System.out.println(collect.getAverage()); // 63.6
// 总和
System.out.println(collect.getSum()); // 318.0
// 总数
System.out.println(collect.getCount()); // 5
}
4.8、joining
joining方法返回的收集器会把流中每个对象应用toString方法得到的所有字符串连接成一个字符串,内部使用了StringBuilder把生成的字符串逐个追加起来。如果Student类中有一个toString方法来返回学生的名称,则无需映射每个学生的名称就能得到相同的结果。
- joining():直接拼接,没有拼接符
- joining(CharSequence delimiter):中间使用指定拼接符进行拼接
比如说:你想把每个学生名称拼接起来
public static void main(String[] args){
// 元数据
List<Student> students = Arrays.asList(
new Student("张三", 20, "100班", 88D),
new Student("李四", 21, "101班", 50D),
new Student("王五", 22, "102班", 44.5D),
new Student("赵六", 20, "103班", 44.5D),
new Student("田七", 21, "103班", 91D)
);
// 1.7及之前写法
String name = "";
for (Student student : students) {
name += student.getName();
}
System.out.println(name); // 张三李四王五赵六田七
// 1.8写法
// joining
String collect = students.stream()
.map(Student::getName)
.collect(Collectors.joining());
System.out.println(collect); // 张三李四王五赵六田七
// joining(CharSequence delimiter)
String collect2 = students.stream()
.map(Student::getName)
.collect(Collectors.joining(","));
System.out.println(collect2); // 张三,李四,王五,赵六,田七
}
4.9、maxBy
根据自定义排序规则,进行定制排序,获取最大值
比如说:你想获取最高分学生的信息
public static void main(String[] args){
// 元数据
List<Student> students = Arrays.asList(
new Student("张三", 20, "100班", 88D),
new Student("李四", 21, "101班", 50D),
new Student("王五", 22, "102班", 44.5D),
new Student("赵六", 20, "103班", 44.5D),
new Student("田七", 21, "103班", 91D)
);
// 1.7及之前写法
Comparator<Student> comparator = new Comparator() {
@Override
public int compare(Object o1, Object o2) {
Student s1 = (Student)o1;
Student s2 = (Student)o2;
return -s1.getScore().compareTo(s2.getScore());
}
};
students.sort(comparator);
System.out.println(students.get(0)); // Student{name='田七', age=21, clazz='103班', score=91.0}
// 1.8写法
// joining
Optional<Student> collect = students.stream()
.collect(Collectors.maxBy(Comparator.comparingDouble(Student::getScore)));
System.out.println(collect); // Optional[Student{name='田七', age=21, clazz='103班', score=91.0}]
}
4.10、minBy
根据自定义排序规则,进行定制排序,获取最小值
比如说:你想获取最低分学生的信息
public static void main(String[] args){
// 元数据
List<Student> students = Arrays.asList(
new Student("张三", 20, "100班", 88D),
new Student("李四", 21, "101班", 50D),
new Student("王五", 22, "102班", 44.5D),
new Student("赵六", 20, "103班", 44.5D),
new Student("田七", 21, "103班", 91D)
);
// 1.7及之前写法
Comparator<Student> comparator = new Comparator() {
@Override
public int compare(Object o1, Object o2) {
Student s1 = (Student)o1;
Student s2 = (Student)o2;
return s1.getScore().compareTo(s2.getScore());
}
};
students.sort(comparator);
System.out.println(students.get(0)); // Student{name='王五', age=22, clazz='102班', score=44.5}
// 1.8写法
// joining
Optional<Student> collect = students.stream()
.collect(Collectors.minBy(Comparator.comparingDouble(Student::getScore)));
System.out.println(collect); // Optional[Student{name='王五', age=22, clazz='102班', score=44.5}]
}
4.11、reducing
有三个重载方法
- Collector<T, ?, T> reducing(T identity, BinaryOperator op):
- Collector<T, ?, Optional> reducing(BinaryOperator op):
- Collector<T, ?, U> reducing(U identity, Function<? super T, ? extends U> mapper,BinaryOperator op)
- 第一个参数是归约操作的起始值,也是流中没有元素时的返回值,所以很显然对于数值和而言,0是一个合适的值
- 第二个参数相当于是map方法,将Student类中的用于计算的数值提取出来
- 第三个参数是一个BinaryOperator,将两个项目累计成一个同类型的值。这里就是对Double类型进行求和
跟上面讲的
reduce
归约基本一样,只有参数有差异比如说:你想获取所有学生的总分
public static void main(String[] args){
// 元数据
List<Student> students = Arrays.asList(
new Student("张三", 20, "100班", 88D),
new Student("李四", 21, "101班", 50D),
new Student("王五", 22, "102班", 44.5D),
new Student("赵六", 20, "103班", 44.5D),
new Student("田七", 21, "103班", 91D)
);
// 1.7及之前写法
// 1.8写法
Double collect1 = students.stream()
.collect(Collectors.reducing(0D, Student::getScore, Double::sum));
System.out.println(collect1); // 318.0
Optional<Double> collect2 = students.stream()
.map(Student::getScore)
.collect(Collectors.reducing(Double::sum));
System.out.println(collect2); // Optional[318.0]
Double aDouble = new Double(82D);
Double collect3 = students.stream()
.map(Student::getScore)
.collect(Collectors.reducing(aDouble, Double::sum));
System.out.println(collect3); // 400.0
}
4.12、collectingAndThen
包裹另一个收集器,对其结果转换函数
比如说,你想使用上面第二种写法,但是又不想自己单独拆掉Optional的包装,可以使用如下方法
public static void main(String[] args){
// 元数据
List<Student> students = Arrays.asList(
new Student("张三", 20, "100班", 88D),
new Student("李四", 21, "101班", 50D),
new Student("王五", 22, "102班", 44.5D),
new Student("赵六", 20, "103班", 44.5D),
new Student("田七", 21, "103班", 91D)
);
// 1.7及之前写法
// 1.8写法
Double collect = students.stream()
.map(Student::getScore)
.collect(Collectors.collectingAndThen(Collectors.reducing(Double::sum), Optional::get));
System.out.println(collect); // 318.0
}
4.13、groupingBy
分组(重要)
根据某属性值对流分组,属性为K结果为V
- groupingBy(Function<? super T, ? extends K> classifier):根据指定数据进行分组
- groupingBy(Function<? super T, ? extends K> classifier, Collector<? super T, A, D> downstream):分好组后,可以对分完组的List进行其他操作,可以通过该方法,对分好组的集合进行统计、取最大值、最小值等操作
- groupingBy(Function<? super T, ? extends K> classifier, Supplier mapFactory, Collector<? super T, A, D> downstream):不晓得,很少用。。。
- 可以进行单级分组,
- 也可以进行多级分组
单级分组
groupingBy(Function<? super T, ? extends K> classifier)
比如说,你想按照班级对各个班的学生进行分组
public static void main(String[] args){
// 元数据
List<Student> students = Arrays.asList(
new Student("张三", 20, "100班", 88D),
new Student("李四", 21, "101班", 50D),
new Student("王五", 22, "102班", 44.5D),
new Student("赵六", 20, "103班", 44.5D),
new Student("田七", 21, "103班", 91D)
);
// 1.7及之前写法
HashMap<String, List<Student>> map = new HashMap<>();
ArrayList<Student> list = new ArrayList<>();
for (Student student : students) {
String clazz = student.getClazz();
List<Student> l = map.get(clazz);
// 如果分组不存在,则创建一个新分组
if (l == null){
ArrayList<Student> lis = new ArrayList<>();
lis.add(student);
map.put(clazz,lis);
continue;
}
l.add(student);
}
/*
key:100班
value:[Student{name='张三', age=20, clazz='100班', score=88.0}]
key:101班
value:[Student{name='李四', age=21, clazz='101班', score=50.0}]
key:102班
value:[Student{name='王五', age=22, clazz='102班', score=44.5}]
key:103班
value:[Student{name='赵六', age=20, clazz='103班', score=44.5}, Student{name='田七', age=21, clazz='103班', score=91.0}]
*/
Set<Map.Entry<String, List<Student>>> entries = map.entrySet();
for (Map.Entry<String, List<Student>> entry : entries) {
System.out.println("key:"+ entry.getKey());
System.out.println("value:"+ entry.getValue());
}
System.out.println("******************************************");
// 1.8写法
Map<String, List<Student>> collect = students.stream()
.collect(Collectors.groupingBy(Student::getClazz));
Set<Map.Entry<String, List<Student>>> entries2 = collect.entrySet();
/*
key:100班
value:[Student{name='张三', age=20, clazz='100班', score=88.0}]
key:101班
value:[Student{name='李四', age=21, clazz='101班', score=50.0}]
key:102班
value:[Student{name='王五', age=22, clazz='102班', score=44.5}]
key:103班
value:[Student{name='赵六', age=20, clazz='103班', score=44.5}, Student{name='田七', age=21, clazz='103班', score=91.0}]
*/
for (Map.Entry<String, List<Student>> entry : entries2) {
System.out.println("key:"+ entry.getKey());
System.out.println("value:"+ entry.getValue());
}
}
groupingBy(Function<? super T, ? extends K> classifier, Collector<? super T, A, D> downstream)
比如说:你想统计每个班的人数
public static void main(String[] args){
// 元数据
List<Student> students = Arrays.asList(
new Student("张三", 20, "100班", 88D),
new Student("李四", 21, "101班", 50D),
new Student("王五", 22, "102班", 44.5D),
new Student("赵六", 20, "103班", 44.5D),
new Student("田七", 21, "103班", 91D)
);
// 1.7及之前写法
HashMap<String, List<Student>> map = new HashMap<>();
ArrayList<Student> list = new ArrayList<>();
for (Student student : students) {
String clazz = student.getClazz();
List<Student> l = map.get(clazz);
// 如果分组不存在,则创建一个新分组
if (l == null){
ArrayList<Student> lis = new ArrayList<>();
lis.add(student);
map.put(clazz,lis);
continue;
}
l.add(student);
}
Set<Map.Entry<String, List<Student>>> entries = map.entrySet();
HashMap<String, Integer> map2 = new HashMap<>();
for (Map.Entry<String, List<Student>> entry : entries) {
map2.put(entry.getKey(),entry.getValue().size());
}
System.out.println(map2); // {100班=1, 101班=1, 102班=1, 103班=2}
System.out.println("******************************************");
// 1.8写法
Map<String, Long> collect = students.stream()
.collect(Collectors.groupingBy(Student::getClazz, Collectors.counting()));
System.out.println(collect); // {100班=1, 101班=1, 102班=1, 103班=2}
}
多级分组
groupingBy(Function<? super T, ? extends K> classifier, Collector<? super T, A, D> downstream)
groupBy方法里面可以嵌套groupBy,进行多级分组
比如说:你想对班级分完组后,对合格和不合格的学生分下组···
public static void main(String[] args){
// 元数据
List<Student> students = Arrays.asList(
new Student("张三", 20, "100班", 88D),
new Student("李四", 21, "100班", 50D),
new Student("王五", 22, "102班", 44.5D),
new Student("赵六", 20, "103班", 44.5D),
new Student("田七", 21, "103班", 91D),
new Student("小八", 21, "103班", 61D)
);
// 1.7及之前写法(如果不想了解的可以直接看下面1.8写法)
HashMap<String, Map<String,List<Student>>> clazzMap = new HashMap<>();
for (Student student : students) {
Map<String, List<Student>> clazzMap2 = clazzMap.get(student.getClazz());
// 判断外部map是否分组
if(clazzMap2 == null){
HashMap<String, List<Student>> cMap = new HashMap<>();
clazzMap.put(student.getClazz(),cMap);
}
// 记录当前学生成绩状态
String isHeGe = "";
if(student.getScore() > 60){
isHeGe = "及格";
}else {
isHeGe = "不及格";
}
// 重新获取分组
clazzMap2 = clazzMap.get(student.getClazz());
// 判断内部map是否分组
List<Student> isPassList = clazzMap2.get(isHeGe);
if (isPassList == null){
ArrayList<Student> sList = new ArrayList<>();
sList.add(student);
clazzMap2.put(isHeGe,sList);
continue;
}
isPassList.add(student);
}
/*
班级:100班
不及格:[Student{name='李四', age=21, clazz='100班', score=50.0}]
及格:[Student{name='张三', age=20, clazz='100班', score=88.0}]
--------------------
班级:102班
不及格:[Student{name='王五', age=22, clazz='102班', score=44.5}]
--------------------
班级:103班
不及格:[Student{name='赵六', age=20, clazz='103班', score=44.5}]
及格:[Student{name='田七', age=21, clazz='103班', score=91.0}, Student{name='小八', age=21, clazz='103班', score=61.0}]
*/
Set<Map.Entry<String, Map<String, List<Student>>>> entrySet = clazzMap.entrySet();
entrySet.forEach(m -> {
System.out.println("--------------------");
System.out.println("班级:" + m.getKey());
Map<String, List<Student>> value = m.getValue();
Set<Map.Entry<String, List<Student>>> entries = value.entrySet();
entries.forEach(map -> {
System.out.println(map.getKey() + ":" + map.getValue());
});
});
System.out.println("******************************************");
// 1.8写法
Map<String, Map<String, List<Student>>> collect = students.stream()
.collect(Collectors.groupingBy(Student::getClazz, Collectors.groupingBy(s -> {
if (s.getScore() >= 60) {
return "及格";
} else {
return "不及格";
}
})));
Set<Map.Entry<String, Map<String, List<Student>>>> entries2 = collect.entrySet();
/*
班级:100班
不及格:[Student{name='李四', age=21, clazz='100班', score=50.0}]
及格:[Student{name='张三', age=20, clazz='100班', score=88.0}]
--------------------
班级:102班
不及格:[Student{name='王五', age=22, clazz='102班', score=44.5}]
--------------------
班级:103班
不及格:[Student{name='赵六', age=20, clazz='103班', score=44.5}]
及格:[Student{name='田七', age=21, clazz='103班', score=91.0}, Student{name='小八', age=21, clazz='103班', score=61.0}]
*/
entries2.forEach(m -> {
System.out.println("--------------------");
System.out.println("班级:" + m.getKey());
Map<String, List<Student>> value = m.getValue();
Set<Map.Entry<String, List<Student>>> entries = value.entrySet();
entries.forEach(map -> {
System.out.println(map.getKey() + ":" + map.getValue());
});
});
}
4.14、partitioningBy
分区
分区的好处在于保留了分区函数返回
true
或false
的两套流元素列表,分区的依据一定要是布尔类型
- partitioningBy(Predicate<? super T> predicate):按照指定的条件分组
- partitioningBy(Predicate<? super T> predicate, Collector<? super T, A, D> downstream):可以对分好的组再次进行操作,可以通过该方法,对分好组的集合进行统计、取最大值、最小值等操作
这里我们在Student类里面新加一个属性,并重新生成有参构造和getter/setter方法
// 是否在校 private Boolean isAtSchool;
单级分区
partitioningBy(Predicate<? super T> predicate)
比如说:你想根据学生是否在校分成两个区域,一个在校的所有学生,一个不在校的学生
public static void main(String[] args){
// 元数据
List<Student> students = Arrays.asList(
new Student("张三", 20, "100班", 88D,true),
new Student("李四", 21, "100班", 50D,true),
new Student("王五", 22, "102班", 44.5D,false),
new Student("赵六", 20, "103班", 44.5D,true),
new Student("田七", 21, "103班", 91D,false),
new Student("小八", 21, "103班", 61D,false)
);
// 1.7及之前写法
HashMap<Boolean, List<Student>> map = new HashMap<>();
for (Student student : students) {
Boolean atSchool = false;
// 先获取学生是否在校
if (student.getAtSchool()){
atSchool = true;
}
List<Student> list = map.get(atSchool);
// 如果没有创建过分组
if(list == null){
ArrayList<Student> l = new ArrayList<>();
l.add(student);
map.put(atSchool,l);
continue;
}
// 创建过分组
list.add(student);
}
Set<Map.Entry<Boolean, List<Student>>> entries1 = map.entrySet();
/*
key:false
value:[Student{name='王五', age=22, clazz='102班', score=44.5, isAtSchool=false},
Student{name='田七', age=21, clazz='103班', score=91.0, isAtSchool=false},
Student{name='小八', age=21, clazz='103班', score=61.0, isAtSchool=false}]
key:true
value:[Student{name='张三', age=20, clazz='100班', score=88.0, isAtSchool=true},
Student{name='李四', age=21, clazz='100班', score=50.0, isAtSchool=true},
Student{name='赵六', age=20, clazz='103班', score=44.5, isAtSchool=true}]
*/
entries1.forEach(entry -> {
System.out.println("key:" + entry.getKey());
System.out.println("value:" + entry.getValue());
});
System.out.println("******************************************");
// 1.8写法
Map<Boolean, List<Student>> collect = students.stream()
.collect(Collectors.partitioningBy(Student::getAtSchool));
Set<Map.Entry<Boolean, List<Student>>> entries = collect.entrySet();
/*
key:false
value:[Student{name='王五', age=22, clazz='102班', score=44.5, isAtSchool=false},
Student{name='田七', age=21, clazz='103班', score=91.0, isAtSchool=false},
Student{name='小八', age=21, clazz='103班', score=61.0, isAtSchool=false}]
key:true
value:[Student{name='张三', age=20, clazz='100班', score=88.0, isAtSchool=true},
Student{name='李四', age=21, clazz='100班', score=50.0, isAtSchool=true},
Student{name='赵六', age=20, clazz='103班', score=44.5, isAtSchool=true}]
*/
entries.forEach(entry -> {
System.out.println("key:" + entry.getKey());
System.out.println("value:" + entry.getValue());
});
}
partitioningBy(Predicate<? super T> predicate, Collector<? super T, A, D> downstream)
比如说:你想知道在校生和不在校生的人数
public static void main(String[] args){
// 元数据
List<Student> students = Arrays.asList(
new Student("张三", 20, "100班", 88D,true),
new Student("李四", 21, "100班", 50D,true),
new Student("王五", 22, "102班", 44.5D,false),
new Student("赵六", 20, "103班", 44.5D,true),
new Student("田七", 21, "103班", 91D,false),
new Student("小八", 21, "103班", 61D,false)
);
// 1.7及之前写法
HashMap<Boolean, List<Student>> map = new HashMap<>();
for (Student student : students) {
// 先获取学生是否在校
Boolean atSchool = student.getAtSchool();
// 尝试获取分组
List<Student> list = map.get(atSchool);
// 如果没有创建过分组
if(list == null){
ArrayList<Student> l = new ArrayList<>();
l.add(student);
map.put(atSchool,l);
continue;
}
// 创建过分组
list.add(student);
}
Set<Map.Entry<Boolean, List<Student>>> entries1 = map.entrySet();
entries1.forEach(entry -> {
/*
false=3
true=3
*/
System.out.println(entry.getKey() + "=" + entry.getValue().size());
});
System.out.println("******************************************");
// 1.8写法
Map<Boolean, Long> collect = students.stream()
.collect(Collectors.partitioningBy(Student::getAtSchool, Collectors.counting()));
System.out.println(collect); // {false=3, true=3}
}
多级分区
partitioningBy(Predicate<? super T> predicate, Collector<? super T, A, D> downstream)
比如说:你想对是否在校的学生分完区后,再根据成绩是否合格进行分区显示
public static void main(String[] args){
// 元数据
List<Student> students = Arrays.asList(
new Student("张三", 20, "100班", 88D,true),
new Student("李四", 21, "100班", 50D,true),
new Student("王五", 22, "102班", 44.5D,false),
new Student("赵六", 20, "103班", 44.5D,true),
new Student("田七", 21, "103班", 91D,false),
new Student("小八", 21, "103班", 61D,false)
);
// 1.7及之前写法
HashMap<Boolean, Map<Boolean,List<Student>>> map = new HashMap<>();
for (Student student : students) {
Boolean atSchool = student.getAtSchool();
Map<Boolean, List<Student>> atSchoolGroupMap = map.get(atSchool);
// 如果没有根据是否在校分过组
if (atSchoolGroupMap == null){
// 创建一个空的内map分组(根据是否合格分组)
HashMap<Boolean, List<Student>> map1 = new HashMap<>();
map.put(atSchool,map1);
}
// 获取当前学生是否合格
Boolean isHeGe = false;
if (student.getScore() > 60){
isHeGe = true;
}
// 重新获取map
Map<Boolean, List<Student>> atSchoolGroupMap2 = map.get(atSchool);
List<Student> stu = atSchoolGroupMap2.get(isHeGe);
// 如果为null,说明没有创建学生是否合格的分组
// 就创建一个集合,把当前新建的分组添加到内map中
if (stu == null) {
ArrayList<Student> list = new ArrayList<>();
list.add(student);
atSchoolGroupMap2.put(isHeGe,list);
// 当前数据处理完毕,处理下一条
continue;
}
// 如果有集合了,就直接添加进去即可
stu.add(student);
}
Set<Map.Entry<Boolean, Map<Boolean, List<Student>>>> entries1 = map.entrySet();
/*
key:false
成绩是否合格:false
结果:[Student{name='王五', age=22, clazz='102班', score=44.5, isAtSchool=false}]
成绩是否合格:true
结果:[Student{name='田七', age=21, clazz='103班', score=91.0, isAtSchool=false},
Student{name='小八', age=21, clazz='103班', score=61.0, isAtSchool=false}]
----------------------------------------
key:true
成绩是否合格:false
结果:[Student{name='李四', age=21, clazz='100班', score=50.0, isAtSchool=true},
Student{name='赵六', age=20, clazz='103班', score=44.5, isAtSchool=true}]
成绩是否合格:true
结果:[Student{name='张三', age=20, clazz='100班', score=88.0, isAtSchool=true}]
*/
entries1.forEach(entrie -> {
System.out.println("----------------------------------------");
System.out.println("key:" + entrie.getKey());
Map<Boolean, List<Student>> value = entrie.getValue();
Set<Map.Entry<Boolean, List<Student>>> val = value.entrySet();
val.forEach( v -> {
System.out.println("成绩是否合格:" + v.getKey());
System.out.println("结果:" + v.getValue());
});
});
System.out.println("***************版本分界线**************************");
// 1.8写法
Map<Boolean, Map<Boolean, List<Student>>> collect = students.stream()
.collect(Collectors.partitioningBy(Student::getAtSchool, Collectors.partitioningBy(s -> {
if (s.getScore() > 60) {
return true;
}
return false;
})));
Set<Map.Entry<Boolean, Map<Boolean, List<Student>>>> entries = collect.entrySet();
/*
key2:false
成绩是否合格:false
结果:[Student{name='王五', age=22, clazz='102班', score=44.5, isAtSchool=false}]
成绩是否合格:true
结果:[Student{name='田七', age=21, clazz='103班', score=91.0, isAtSchool=false},
Student{name='小八', age=21, clazz='103班', score=61.0, isAtSchool=false}]
----------------------------------------
key:true
成绩是否合格:false
结果:[Student{name='李四', age=21, clazz='100班', score=50.0, isAtSchool=true},
Student{name='赵六', age=20, clazz='103班', score=44.5, isAtSchool=true}]
成绩是否合格:true
结果:[Student{name='张三', age=20, clazz='100班', score=88.0, isAtSchool=true}]
*/
entries.forEach(entrie -> {
System.out.println("----------------------------------------");
System.out.println("key:" + entrie.getKey());
Map<Boolean, List<Student>> value = entrie.getValue();
Set<Map.Entry<Boolean, List<Student>>> val = value.entrySet();
val.forEach( v -> {
System.out.println("成绩是否合格" + v.getKey());
System.out.println("结果" + v.getValue());
});
});
}