一、关于Stream

Stream 是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。使用 Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。也可以使用 Stream API 来并行执行操作。简言之,Stream API 提供了一种高效且易于使用的处理数据的方式



二、什么是Stream

Stream 是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列

需要注意的是:

  1. Stream 自己不会存储元素。
  2. Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream。
  3. Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。


三、Stream操作的三个步骤
  1. 创建Stream:可以通过集合、数组等数据源,来创建一个 Stream 流
  2. 中间操作:中间操作链队数据源的数据进行处理
  3. 终止操作:一旦执行终止操作,就执行中间操作链,并产生结果,之后被执行的 Stream 流便不再被使用


四、创建Stream的几种方式
1. 通过集合创建Stream

Java8 的 Collection 接口有两个获取流的 default 方法,用 default 修饰,因为 List 和 Set 是 Collection 接口的子接口,所以 List,Set 的所有实现类的对象能够直接调用 Collection 中的这两个 default 方法

default Stream<E> stream() {
    return StreamSupport.stream(spliterator(), false);
}

default Stream<E> parallelStream() {
    return StreamSupport.stream(spliterator(), true);
}

其中 stream() 方法返回一个顺序流,parallelStream() 方法返回一个并行流

举个例子:

@Test
public void test01() {
    List<Employee> employees = EmployeeData.getEmployees();

    // 调用 Collection 接口中的 stream() 方法
    Stream<Employee> stream = employees.stream();
    System.out.println(stream);

    // 调用 Collection 接口中的 parallelStream() 方法
    Stream<Employee> employeeStream = employees.parallelStream();
    System.out.println(employeeStream);
}



2. 通过数组创建Stream

通过 Java8 中的 Arrays 类中的静态方法 stream() 获取数组流

public static IntStream stream(int[] array) {
    return stream(array, 0, array.length);
}

public static LongStream stream(long[] array) {
    return stream(array, 0, array.length);
}

举个例子:

@Test
public void test02() {
    int[] arr = {1, 2, 3, 4, 5};
    // 调用 Arrays 类的 static IntStream stream(int[] array) 方法返回一个 Stream
    IntStream stream = Arrays.stream(arr);

    Employee employee = new Employee(100, "employee");
    Employee employee1 = new Employee(101, "employee2");
    Employee[] employees = {employee, employee1};

    Stream<Employee> stream1 = Arrays.stream(employees);
}



3. 通过Stream的of()方法创建Stream

Stream 类下的 of(T... values) 方法

public static<T> Stream<T> of(T... values) {
    return Arrays.stream(values);
}

举个例子

@Test
public void test03() {
    Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);
}



五、Stream的中间操作

多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理,而在终止操作时一次性全部处理,这个过程称为惰性求值

1. 筛选与切片

共有如下几种方法:

  • filter(Predicate p):接收 Lambda,从流中排除某些元素
  • distinct():通过流所生成元素的 hashCode() 和equals() 去除重复元素
  • limit(long maxSize):截断流,返回给定数量的流
  • skip(long n):跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个, 则返回一个空流。与 limit(n) 互补
@Test
public void test01() {
    List<Employee> employees = EmployeeData.getEmployees();
    Stream<Employee> stream = employees.stream();

    // 查询员工表中工资超过7000的员工的信息
    stream.filter(employee -> employee.getSalary() > 7000).forEach(System.out::println);

    System.out.println();

    // 查询员工表中的前5条记录
    employees.stream().limit(5).forEach(System.out::println);

    System.out.println();

    // 查询员工表中去除前5条记录之后的结果
    employees.stream().skip(5).forEach(System.out::println);

    System.out.println();

    // 去除重复, 需要根据筛选对象重写的hasCode()和equals()方法来去除
    employees.stream().distinct().forEach(System.out::println);
}



2. 映射

共有以下几种方法

  • map(Function f):接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的 Stream
  • mapToDouble(ToDoubleFunction f):接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 DoubleStream
  • mapToInt(ToIntFunction f):接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 IntStream
  • flatMap(Function f):接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流
@Test
public void test02() {
    List<String> list = Arrays.asList("aa", "bb", "cc", "dd");
    // 位置1
    Stream<String> stringStream = list.stream().map(s -> s.toUpperCase());
    stringStream.forEach(System.out::println);

    List<Employee> employees = EmployeeData.getEmployees();
    // 位置2
    Stream<String> nameStream = employees.stream().map(Employee::getName);
    nameStream.filter(name -> name.length() > 3).forEach(System.out::println);

    System.out.println();

    // 位置3
    Stream<Character> characterStream = list.stream().flatMap(StreamAPITest01::fromStringToStream);
    characterStream.forEach(System.out::print);
}

位置1先获取集合的流,然后将流中的每个元素转换成大写的元素,最终将所有转换完成的元素映射成一个新的流

位置2先获取 Employee 集合的流,然后获取每个 Employee 对象的 name 属性,最终将获取到的所有 name 映射成一个流

位置3先获取集合的流,然后在 flatMap 中传入另外一个流,最后将所有的流连接成为一个流



3. 排序

共有以下几种方法

  • sorted():产生一个新的流,其中按自然顺序排序
  • sorted(Comparator com):产生一个新的流,其中按比较器顺序进行排序
@Test
public void test04() {
    // 自然排序
    List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
    list.stream().sorted().forEach(integer -> System.out.print(integer + " "));

    // 因为Employee没有实现Comparable接口, 此时抛出异常
    /*List<Employee> employees1 = EmployeeData.getEmployees();
    employees1.stream().sorted().forEach(System.out::print);*/

    List<Employee> employees = EmployeeData.getEmployees();
    employees.stream().sorted((o1, o2) -> Integer.compare(o1.getAge(), o2.getAge())).forEach(System.out::print);
}

如果获取到的是基本类型的流,则直接使用 sorted() 方法对其中的元素进行排序;如果获取到的是对象类型的流,则对象需要实现 Comparable 接口,并重写里面的 compareTo(T o) 方法



六、Stream的终止操作

终端操作会从流的流水线生成结果,其结果可以是任何不是流的值,一个流执行了终止操作之后,就不能被再次使用了

1. 匹配和查找

共有以下几种方法:

  • allMatch(Predicate p):检查是否匹配所有的元素
  • anyMatch(Predicate p):检查是否至少匹配一个元素
  • noneMatch(Predicate p):检查是否没有匹配所有元素
  • findFirst():返回第一个元素
  • findAny():返回当前流中的任意元素
  • count():返回流中元素的总数
  • max(Comparator c):返回流中的最大值
  • min(Comparator c):返回流中的最小值
  • forEach(Consumer c):内部迭代器
@Test
public void test01() {
    // 使用 allMatch 检查是否所有员工的年龄是否大于18
    boolean b = employees.stream().allMatch(e -> e.getAge() > 18);
    System.out.println(b);

    // 使用 anyMatch 检查是否存在员工的工资大于 1000
    boolean b1 = employees.stream().anyMatch(e -> e.getAge() > 1000);
    System.out.println(b1);

    // 使用 noneMatch 查询是否没有员工姓磊
    boolean b2 = employees.stream().noneMatch(e -> e.getName().startsWith("磊"));
    System.out.println(b2);

    // 使用 findFirst
    Optional<Employee> employee = employees.stream().findFirst();
    System.out.println(employee);

    // 使用 findAny
    Optional<Employee> any = employees.stream().findAny();
    System.out.println(any);

    // 使用 count
    long count = employees.stream().filter(e -> e.getSalary() > 5000).count();
    System.out.println(count);

    // 使用 max
    Optional<Employee> max = employees.stream().distinct()
            .max((o1, o2) -> o1.getAge() - o2.getAge());
    System.out.println(max);

    Stream<Integer> salaryStream = employees.stream().map(Employee::getAge);
    Optional<Integer> max1 = salaryStream.max(Integer::compare);
    System.out.println(max1);

    // 使用 min
    Optional<Employee> min = employees.stream().min((o1, o2) -> Integer.compare(o1.getAge(), o2.getAge()));
    System.out.println(min);
}



2. 归约

共有以下几种方法:

  • reduce(T identity, BinaryOperator<T> accumulator):将流中元素反复结合起来,得到一个值,返回类型 T
  • reduce(BinaryOperator<T> accumulator):可以将流中元素反复结合起来,得到一个值。返回 Optional<T>
@Test
public void test02() {
    List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
    // 位置1
    Integer reduce = list.stream().reduce(0, Integer::sum);
    System.out.println(reduce);

    // 位置2
    Optional<Double> reduce1 = employees.stream().map(Employee::getSalary).reduce(Double::sum);
    System.out.println(reduce1);
}

位置1表示对流中的元素,获取第0位以及之后所有元素相加的值;位置2则先获取流中的 Employee 的 salary,然后再对所有的 salary 执行求和操作



3. 收集

共有以下几种方法:

  • collect(Collector c):将流转换为其他形式。接收一个 Collector 接口的实现,将 Stream 中的元素转换成想要的类型

其中 Collector 接口中常用下面几种方法:

  • toList:返回类型是 List,将流中的元素收集到 List 中
  • toSet:返回类型是 Set,将流中的元素手机到 Set 中
  • toCollection:返回类型是 Collection,将流中元素收集到创建的集合中
@Test
public void test03() {
    List<Employee> collect = employees.stream().filter(employee -> employee.getSalary() > 6000)
            .collect(Collectors.toList());
    collect.forEach(System.out::println);

    System.out.println();

    Set<Employee> collect1 = employees.stream().filter(employee -> employee.getSalary() > 6000)
            .collect(Collectors.toSet());
    collect1.forEach(System.out::println);

    System.out.println();

    Collection<Employee> collect2 = employees.stream().filter(employee -> employee.getSalary() > 6000)
            .collect(Collectors.toCollection(ArrayList::new));
    collect2.forEach(System.out::println);
}