Java Stream学习笔记总结

127 阅读8分钟

Java8中有两大最为重要的改变。第一个是 Lambda 表达式;另外一个则 是 Stream API。

Stream API ( java.util.stream) 把真正的函数式编程风格引入到Java中。这 是目前为止对Java类库最好的补充,因为Stream API可以极大提供Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。

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

实际开发中,项目中多数数据源都来自于Mysql,Oracle等。但现在数据源可以更多了,有MongDB,Radis等,而这些NoSQL的数据就需要 Java层面去处理。

Stream 和 Collection 集合的区别:Collection 是一种静态的内存数据 结构,而 Stream 是有关计算的。前者是主要面向内存,存储在内存中, 后者主要是面向 CPU,通过 CPU 实现计算。

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

Stream 自己不会存储元素。

Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream。

Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。

一、创建Stream

1、通过集合创建

Java8 中的 Collection 接口被扩展,提供了两个获取流 的方法:

  • default Stream stream() : 返回一个顺序流
  • default Stream parallelStream() : 返回一个并行流

    List<Integer> list = new ArrayList<Integer>(){{
        add(1);
        add(2);
        add(3);
        add(4);
        add(5);
    }};

    Stream<Integer> stream1 =  list.stream();
    Stream<Integer> stream2 = list.parallelStream();

2、通过数组创建

Java8 中的 Arrays 的静态方法 stream() 可以获取数组流

static Stream stream(T[] array): 返回一个流

    Integer[] arr1 = new Integer[]{1,2,3,4,5};
    Stream<Integer> stream3 = Arrays.stream(arr1);

重载形式,能够处理对应基本类型的数组

  • public static IntStream stream(int[] array)
  • public static LongStream stream(long[] array)
  • public static DoubleStream stream(double[] array
    int[] arr2 = new int[]{1,2,3,4,5};
    IntStream stream4 = Arrays.stream(arr2);
    
    double[] arr3 = new double[]{1.0,2.0,3.0,4.0,5.0};
    DoubleStream stream5 = Arrays.stream(arr3);

3、通过Stream的of()

可以调用Stream类静态方法 of(), 通过显示值创建一个 流。它可以接收任意数量的参数。

public static Stream of(T... values) : 返回一个流

    Stream<String> stream6 = Stream.of("a","b","c");
    
    Stream<Integer> stream7 = Stream.of(1,2,3,4,5);

4、创建无限流

可以使用静态方法 Stream.iterate() 和 Stream.generate(), 创建无限流。

  • 迭代 public static Stream iterate(final T seed, final UnaryOperator f)
  • 生成 public static Stream generate(Supplier s)
    Stream<Integer> stream8 = Stream.iterate(0, x -> x + 2); // 无限流
    stream8.limit(10).forEach(System.out::println);

二、Stream的中间操作

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

首先准备测试数据

public class Employee {
    private String name;
    private int age;
    private double salary;

    public Employee() {
    }
    public Employee(String name, int age, double salary) {
        this.name = name;
        this.age = age;
        this.salary = salary;
    }

    public List<Employee> getEmployees(){
        return List.of(
                new Employee("张三", 18, 9999.99),
                new Employee("李四", 38, 5555.99),
                new Employee("王五", 50, 6666.66),
                new Employee("赵六", 16, 3333.33),
                new Employee("田七", 8, 7777.77)
        );
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }
// 重写equals和hashCode方法
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Employee employee = (Employee) o;
        return age == employee.age && Double.compare(salary, employee.salary) == 0 && Objects.equals(name, employee.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age, salary);
    }
    
    
    @Override
    public String toString() {
        return "Employee{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", salary=" + salary +
                '}';
    }
}

1、筛选与切片

方法描述
distinct()筛选,通过流所生成元素的 hashCode() 和 equals() 去除重复元素
filter(Predicate p)接收 Lambda , 从流中排除某些元素
limit(long maxSize)截断流,使其元素不超过给定数量
skip(long n)跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则返回一 个空流。与 limit(n) 互补

测试

筛选出薪水大于6000的员工

        List<Employee> employees = new Employee().getEmployees();
        Stream<Employee> stream = employees.stream();
        // 筛选出薪水大于6000的员工
        stream.filter(e -> e.getSalary() > 6000).forEach(System.out::println);
Employee{name='张三', age=18, salary=9999.99}
Employee{name='王五', age=50, salary=6666.66}
Employee{name='田七', age=8, salary=7777.77}

查看前两个

        List<Employee> employees = new Employee().getEmployees();
        Stream<Employee> stream = employees.stream();
        stream.limit(2).forEach(System.out::println);

跳过前3个,查看后面的

        List<Employee> employees = new Employee().getEmployees();
        Stream<Employee> stream = employees.stream();
        stream.skip(3).forEach(System.out::println);
Employee{name='赵六', age=16, salary=3333.33}
Employee{name='田七', age=8, salary=7777.77}

去除重复的元素

需要重写equals和hashCode方法

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Employee employee = (Employee) o;
        return age == employee.age && Double.compare(salary, employee.salary) == 0 && Objects.equals(name, employee.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age, salary);
    }
        List<Employee> employees = new ArrayList<>();
        employees.add(new Employee("田七", 8, 7777.77));
        employees.add(new Employee("田七", 8, 7777.77));
        employees.add(new Employee("田七", 8, 7777.77));
        Stream<Employee> stream = employees.stream();

        stream.distinct().forEach(System.out::println);
Employee{name='田七', age=8, salary=7777.77}

2、映射

方法描述
map(Function f)接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
mapToDouble(ToDoubleFunction f)接收一个函数作为参数,该函数会被应用到每个元 素上,产生一个新的 DoubleStream
mapToInt(ToIntFunction f)接收一个函数作为参数,该函数会被应用到每个元 素上,产生一个新的 IntStream
mapToLong(ToLongFunction f)接收一个函数作为参数,该函数会被应用到每个元 素上,产生一个新的 LongStream
flatMap(Function f)接收一个函数作为参数,将流中的每个值都换成另 一个流,然后把所有流连接成一个流

获取姓名长度大于等于3的员工的姓名

        List<Employee> employees = new Employee().getEmployees();
// map 后得到的是姓名的List
        employees.stream().map(Employee:: getName).filter(name -> name.length() >= 3).forEach(System.out::println);

3、排序

方法描述
sorted()产生一个新流,其中按自然顺序排序
sorted(Comparator com)产生一个新流,其中按比较器顺序排序

获取按年龄升序排序的员工的年龄

        List<Employee> employees = new Employee().getEmployees();
// 获取年龄再排序
        employees.stream().map(Employee::getAge).sorted().forEach(System.out::println);

获取按年龄升序排序的员工的信息

重写比较规则

        List<Employee> employees = new Employee().getEmployees();
        // 按年龄升序输出员工信息
        employees.stream().sorted((e1, e2) -> Integer.compare(e1.getAge(), e2.getAge())).forEach(System.out::println);

其中Integer.compare方法如下

    public static int compare(int x, int y) {
        return (x < y) ? -1 : ((x == y) ? 0 : 1);
    }

按薪水排序

        List<Employee> employees = new Employee().getEmployees();
        employees.stream().sorted((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary())).forEach(System.out::println);
Employee{name='赵六', age=16, salary=3333.33}
Employee{name='李四', age=38, salary=5555.99}
Employee{name='王二五', age=50, salary=6666.66}
Employee{name='田七', age=8, salary=7777.77}
Employee{name='张三三', age=18, salary=9999.99}

按姓名排序

String.CASE_INSENSITIVE_ORDER 字符串不区分大小写

        List<Employee> employees = new Employee().getEmployees();
        employees.stream().sorted((e1, e2) -> String.CASE_INSENSITIVE_ORDER.compare(e1.getName(), e2.getName())).forEach(System.out::println);
Employee{name='Alice', age=8, salary=7777.77}
Employee{name='Bob', age=16, salary=3333.33}
Employee{name='peter', age=18, salary=9999.99}
Employee{name='sam', age=38, salary=5555.99}
Employee{name='white', age=50, salary=6666.66}

三、Stream的终止操作

终端操作会从流的流水线生成结果。其结果可以是任何不是流的值,例 如:List、Integer,甚至是 void 。流进行了终止操作后,不能再次使用。

1、匹配与查找

方法描述
allMatch(Predicate p)检查是否匹配所有元素
anyMatch(Predicate p)检查是否至少匹配一个元素
noneMatch(Predicate p)检查是否没有匹配所有元素
findFirst()返回第一个元素
findAny()返回当前流中的任意元素
count()返回流中元素总数
max(Comparator c)返回流中最大值,需要实现Comparator接口
min(Comparator c)返回流中最小值
forEach(Consumer c)内部迭代(使用 Collection 接口需要用户去做迭代, 称为外部迭代。相反,Stream API 使用内部迭代——它帮你把迭代做了)

检查所有的员工是否都年满18岁

        List<Employee> employees = new Employee().getEmployees();
        boolean flag = employees.stream().allMatch(e -> e.getAge() >= 18);
        System.out.println(flag);

检查是否至少有员工大于50岁

        List<Employee> employees = new Employee().getEmployees();
        boolean flag = employees.stream().anyMatch(e -> e.getAge() >= 50);
        System.out.println(flag);

判断是否所有的员工年龄都大于18岁

        List<Employee> employees = new Employee().getEmployees();
        // 使用noneMatch
        boolean flag = employees.stream().noneMatch(e -> e.getAge() <= 18); // 作用是判断是否所有的员工年龄都大于18岁

计算员工总数

        List<Employee> employees = new Employee().getEmployees();
        long count = employees.stream().count();
        System.out.println(count);

求最多薪水的员工

        List<Employee> employees = new Employee().getEmployees();
        System.out.println(employees.stream().max((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary())));

2、归约

方法描述
reduce(T iden, BinaryOperator b)可以将流中元素反复结合起来,得到一 个值。返回 T
reduce(BinaryOperator b)可以将流中元素反复结合起来,得到一 个值。返回 Optional

求1-10的和

使用方法引用更方便简洁

        List<Integer> nums= Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
        // lambda表达式写法
        System.out.println(nums.stream().reduce(0, (x1, x2) -> x1 + x2));
        // 方法调用
        System.out.println(nums.stream().reduce(0, (x1, x2) -> Integer.sum(x1, x2)));
        // 方法引用
        System.out.println(nums.stream().reduce(0, Integer::sum));

求所有员工的薪水总和

        List<Employee> employees = new Employee().getEmployees();
        System.out.println(employees.stream().map(Employee::getSalary).reduce(Double::sum));
Optional[33333.740000000005] 得到的是一个Optional
        List<Employee> employees = new Employee().getEmployees();
        System.out.println(employees.stream().map(Employee::getSalary).reduce(Double::sum).get()); // 调用get方法就可以得到总薪水

3、收集

方法描述
collect(Collector c)将流转换为其他形式。接收一个 Collector 接口的实现,用于给Stream中元素做汇总 的方法

Collector 接口中方法的实现决定了如何对流执行收集的操作(如收集到 List、Set、 Map)。 另外, Collectors 实用类提供了很多静态方法,可以方便地创建常见收集器实例, 具体方法与实例如下表:

查找薪水大于6000的员工,将结果返回为一个List

        List<Employee> employees = new Employee().getEmployees();
        List<Employee> collect = employees.stream().filter(e -> e.getSalary() > 6000).toList();
        collect.forEach(System.out::println);
Employee{name='peter', age=18, salary=9999.99}
Employee{name='white', age=50, salary=6666.66}
Employee{name='Alice', age=28, salary=7777.77}

根据年龄升序排序,返回List

        List<Employee> employees = new Employee().getEmployees();
        List<Employee> list1 = employees.stream().sorted((e1, e2) -> Integer.compare(e1.getAge(), e2.getAge())).toList();
        list1.forEach(System.out::println);