Java8-Stream流详细教程

前言

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来简化上面代码

  1. 我们使用split方法获取到的是一个数组,我们上面学习到,可以使用Arrays.stream()方法,把数组转换为一个Stream流
  2. 转换为流后,我们的返回值就会为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

分区

分区的好处在于保留了分区函数返回truefalse的两套流元素列表,分区的依据一定要是布尔类型

  • 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());
        });
    });
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java 8引入了Stream API,它是一种基于lambda表达式的函数式编程方式,用于处理集合数据。Stream API提供了许多操作,例如过滤、映射、排序、归约等等,使得对集合数据的处理变得更加简单和高效。 以下是一些基本的Stream操作: 1. 创建Stream 可以通过Collection和Arrays类的stream()方法来创建一个Stream: ```java List<String> list = Arrays.asList("apple", "banana", "orange"); Stream<String> stream = list.stream(); ``` 2. 过滤数据 可以使用filter()方法过滤Stream中的数据: ```java List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); Stream<Integer> stream = numbers.stream().filter(n -> n % 2 == 0); ``` 3. 映射数据 可以使用map()方法来对Stream中的数据进行映射: ```java List<String> list = Arrays.asList("apple", "banana", "orange"); Stream<String> stream = list.stream().map(s -> s.toUpperCase()); ``` 4. 排序数据 可以使用sorted()方法来对Stream中的数据进行排序: ```java List<Integer> numbers = Arrays.asList(1, 3, 2, 5, 4, 7, 6, 9, 8, 10); Stream<Integer> stream = numbers.stream().sorted(); ``` 5. 归约数据 可以使用reduce()方法来对Stream中的数据进行归约: ```java List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); int sum = numbers.stream().reduce(0, (a, b) -> a + b); ``` 以上是一些基本的Stream操作,通过这些操作,我们可以轻松地对集合数据进行处理。如果您想深入了解Stream API,请参考Java官方文档或其他相关教程

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值