Stream API学习笔记

Java
156
0
0
2024-04-17

Java8 中Stream API介绍

Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。元素流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前面处理的结果。

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

注意:

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

Stream的操作三个步骤:

  • 创建Stream:一个数据源(如数组、集合),获取一个流
  • 中间操作:一个中间操作链,对数据源的数据进行处理
  • 终止操作(终端操作):一个终止操作,执行中间操作链,并产生结果
创建Stream

创建流的方式有如下几种方式:

    //创建Stream
    @Test
    public void test01(){
        //1.可以通过Collection系列集合提供的stream() 或 parallelStream()
        List<String> list = new ArrayList();
        Stream<String> stream01 = list.stream();

        //2、通过Arrays中的静态方法stream() 获取数组流
        Emp[] emps = new Emp[10];
        Stream<Emp> stream02 = Arrays.stream(emps);

        //3.通过Stream类中的静态方法of()
        Stream<String> stream03 = Stream.of("aa","bb","cc");

        //4.创建无限流
        //迭代
        Stream<Integer> stream04 = Stream.iterate(0,(x) -> x+2);
        stream04.forEach(System.out::println);
        //只要10个数据
        stream04.limit(10).forEach(System.out::println);

        //生成5个随机数
        Stream.generate(() -> Math.random()).limit(5).forEach(System.out::println);
    }
Stream中间操作

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

筛选与切片:

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

映射:

  • map ------ 接收Lambda,将元素转换为其他形式或提取信息(接受一个函数作为参数,该函数被应用到每个元素上,并将其映射成一个新的元素)
  • flatMap ---- 接收一个函数作为参数,将流中的每个值都换成另外一个流,然后把所有的流连凑成一个流。

排序:

  • sorted() ---- 自然排序
  • sorted(Comparator comparator) ------ 定制排序(Comparator )

下面通过代码来练习这些中间操作,先创建一个Employee实体类:

public class Employee {

    private String name;

    private Integer age;

    private Double salary;

    public Employee(String name, Integer age, Double salary) {
        this.name = name;
        this.age = age;
        this.salary = salary;
    }

    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 Double getSalary() {
        return salary;
    }

    public void setSalary(Double salary) {
        this.salary = salary;
    }

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

    @Override
    public int hashCode() {
        return Objects.hash(name, age, salary);
    }

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

测试中间操作filter的用法:

    List<Employee> emps = Arrays.asList(
            new Employee("张三",21,4500.00),
            new Employee("李四",25,6000.00),
            new Employee("王五",56,3500.00),
            new Employee("王五",56,3500.00),
            new Employee("田七",30,8000.00),
            new Employee("田七",30,8000.00)
    );

    @Test
    public void test02(){
        //中间操作:不会执行任何操作
        Stream<Employee> stream = employeeList.stream().filter(e -> e.getAge()>25);
        //终止操作:一次型执行全部内容,即”惰性求值“
        //内部迭代:迭代操作由Stream API 完成
        stream.forEach(System.out::println);
    }

    //外部迭代
    @Test
    public void test03(){
        Iterator<Employee> it = emps.iterator();
        while(it.hasNext()){
            System.out.println(it.next());
        }
    }

中间操作:limit --只要找到符合条件的指定条数数据,就不会执行后面的数据过滤操作了,可以提高效率

    @Test
    public void test04(){
        emps.stream().filter(e -> e.getSalary()>3000.00)
                .limit(2) //短路 : 只要找到符合条件的两条数据,就不会执行后面的数据过滤操作了,可以提高效率
                .forEach(System.out::println);
    }

中间操作:skip(n) ----跳过n个元素,返回一个扔掉了前n个元素的流,若流中元素不足n个,则返回一个空流,与limit(n)互补

    @Test
    public void test05(){
        emps.stream().filter(e -> e.getSalary()>3000.00)
                .skip(2)
                .forEach(System.out::println);
    }

中间操作: distinct ---- 筛选,通过流所生成元素的hashCode() 和 equals() 去除重复元素

    @Test
    public void test06(){
        emps.stream().filter(e -> e.getSalary()>3000.00)
                .distinct ()
                .forEach(System.out::println);
    }

中间操作: map ------ 接受一个函数作为参数,该函数或被应用到每个元素上,并将其映射成一个新的元素

    @Test
    public void test07(){
        List<String> list = Arrays.asList("aaa","bbb","ccc","ddd","hello");
        list.stream()
                //toUpperCase()函数被应用到流中每个元素上,并将其映射成一个新的元素
                .map((str) -> str.toUpperCase())
                .forEach(System.out::println); //输出结果:AAA BBB CCC DDD HELLO
        System.out.println("------------------------------");

        emps.stream()
                .map(Employee::getName)
                .forEach(System.out::println);//输出结果:张三 李四 王五 王五 田七 田七
        System.out.println("------------------------------");

        Stream<Stream<Character>> stream01 = list.stream()
                //调用filterCharacter(),将流中的字符串元素都转为字符流,返回值类型为Stream<Stream<Character>>
                .map(StreamApiTest::filterCharacter);
        stream01.forEach((sm) -> {
            sm.forEach(System.out::println);
        });
        System.out.println("------------------------------");
    }

    //字符串转为字符流
    public static Stream<Character> filterCharacter(String str){
        List<Character> list = new ArrayList<>();
        for(Character ch : str.toCharArray()){
            list.add(ch);
        }
        return list.stream();
    }

中间操作: flatMap — 接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有的流连凑成一个流

    @Test
    public void test08(){
        List<String> list = Arrays.asList("aaa","bbb","ccc","ddd","hello");
        Stream<Character> stream02 = list.stream()
                //调用filterCharacter(),将流中的字符串元素都转为字符流,并将这些流加入到一个新流中,返回值类型为Stream<Character>
                .flatMap(StreamApiTest::filterCharacter);
        stream02.forEach(System.out::println);
    }

中间操作: sorted() ---- 自然排序

    @Test
    public void test09(){
        List<String> list  = Arrays.asList("ccc","aaa","ddd","bbb","eee");
        list.stream().sorted().forEach(System.out::println);
    }

中间操作: sorted(Comparator comparator) ------ 定制排序(Comparator ) 自定义排序规则,这个根据员工年龄排序,若员工年龄相同,则根据员工姓名排序 — 升序

    @Test
    public void test10(){
        emps.stream()
                .sorted((e1,e2) -> {
                    if(e1.getAge().equals(e2.getAge())){
                        return e1.getName().compareTo(e2.getName());
                    }else{
                        return e1.getAge().compareTo(e2.getAge());
                    }
                })
                .forEach(System.out::println);
    }
终止操作

查找与匹配:

  • allMatch ----- 检查是否匹配所有元素
  • anyMatch ------ 检查是否至少匹配一个元素
  • noneMatch --------- 检查是否没有匹配所有元素
  • findFirst ------- 返回第一个元素
  • findAny -------- 返回流中的任意元素
  • count ----------- 返回流中元素的总个数
  • max ------- 返回流中的最大值
  • min ------ 返回流中的最小值

归约:可以将流中元素反复结合起来,得到一个值

  • reduce(T indentity,BinaryOperator bin) ---- indentity 为起始值
  • reduce(BinaryOperator bin)

收集:

  • collect ----- 将流装换为其它形式,接受一个Collector接口的实现,用于给Stream中元素汇总的方法

终止操作练习:在此之前,我们先创建一个员工实体类,方便测试效果

public class Employee {

    private String name;

    private Integer age;

    private Double salary;

    private Status status;

    public Employee(String name, Integer age, Double salary) {
        this.name = name;
        this.age = age;
        this.salary = salary;
    }

    public Employee(String name, Integer age, Double salary, Status status) {
        this.name = name;
        this.age = age;
        this.salary = salary;
        this.status = status;
    }

    public Status getStatus() {
        return status;
    }

    public void setStatus(Status status) {
        this.status = status;
    }

    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 Double getSalary() {
        return salary;
    }

    public void setSalary(Double salary) {
        this.salary = salary;
    }

    @Override
    public String toString() {
        return "Employee{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", salary=" + salary +
                ", status=" + status +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Employee)) return false;
        Employee employee = (Employee) o;
        return Objects.equals(getName(), employee.getName()) &&
                Objects.equals(getAge(), employee.getAge()) &&
                Objects.equals(getSalary(), employee.getSalary()) &&
                getStatus() == employee.getStatus();
    }

    @Override
    public int hashCode() {

        return Objects.hash(getName(), getAge(), getSalary(), getStatus());
    }

    public enum Status{
        BUSY,FREE,VACATION;
    }
}

接下来我们先对 查找与匹配 中的几个终止操作进行代码测试:

    List<Employee> employees = Arrays.asList(
            new Employee("张三",21,4500.00, Employee.Status.BUSY),
            new Employee("李四",25,6000.00,Employee.Status.VACTION),
            new Employee("王五",56,3500.00,Employee.Status.BUSY),
            new Employee("王五",56,3500.00,Employee.Status.BUSY),
            new Employee("田七",30,8000.00,Employee.Status.FREE),
            new Employee("田七",30,8000.00,Employee.Status.FREE)
    );

    @Test
    public void test11(){
        //allMatch ----- 检查是否匹配所有元素
        boolean b1 = employees.stream().allMatch((e) -> e.getStatus().equals(Employee.Status.FREE));
        System.out.println(b1); //输出结果: false

        //anyMatch ------ 检查是否至少匹配一个元素
        boolean b2 = employees.stream().anyMatch((e) -> e.getStatus().equals(Employee.Status.BUSY));
        System.out.println(b2); //输出结果: true

        //noneMatch --------- 检查是否没有匹配所有元素
        boolean b3 = employees.stream().noneMatch((e) -> e.getStatus().equals(Employee.Status.BUSY));
        System.out.println(b3); //输出结果: false

        //findFirst ------- 返回第一个元素
        //Optional --- 是Java8 提供的处理空指针异常的类
        Optional<Employee> employee = employees.stream()
                //按员工的薪资排序
                .sorted((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()))
                //获取第一个员工信息,即薪资最新的员工信息
                .findFirst();
        System.out.println(employee.get());

        //findAny -------- 返回流中的任意元素
        //parallelStream --- 获取并行流
        Optional<Employee> any = employees.parallelStream()
                //获取状态为 FREE 的任意一个员工信息
                .filter(e -> e.getStatus().equals(Employee.Status.FREE))
                .findAny();
        System.out.println(any.get());
    }

测试终止操作中,count,max,min的运用

    @Test
    public void test12(){
        //返回流中元素的总个数
        long count = employees.stream().count();
        System.out.println(count); //输出结果为:6

        Optional<Employee> max = employees.stream().
                //获取年龄最大的员工信息
                max((e1, e2) -> Integer.compare(e1.getAge(), e2.getAge()));
        System.out.println(max.get()); // 输出结果为:Employee{name='王五', age=56, salary=3500.0, status=BUSY}

        Optional<Double> min = employees.stream()
                .map((e) -> e.getSalary())
                //获取最低的薪资
                .min(Double::compare);
        System.out.println(min.get());//输出结果为:3500.0
    }

终止操作:归约 ---- reduce(T indentity,BinaryOperator) / reduce(BinaryOperator),可以将流中元素反复结合起来,得到一个值

    @Test
    public void test13(){
        List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
        Integer reduce = list.stream()
                //0为初始值,将流中的元素按照 lambda体中的方式进行汇总,这里即是通过求和的方式汇总
                .reduce(0, (x, y) -> x + y);
        System.out.println(reduce); // 输出结果为:55

        Optional<Double> sum = employees.stream()
                //获取员工的薪资信息
                .map((e) -> e.getSalary())
                //调用Double的sum(),对员工薪资进行求和
                .reduce(Double::sum);
        System.out.println(sum.get()); //输出结果为:33500.0
    }

终止操作:收集:collect ------- 将流装换为其它形式,接收一个Collector 接口的实现,用于给Stream中元素汇总的方法

    @Test
    public void test14(){

        //Collectors工具类对Collector接口提供了很多实现
        List<String> list = employees.stream()
                .map(e -> e.getName())
                .collect(Collectors.toList());
        System.out.println(list);//输出结果为:[张三, 李四, 王五, 王五, 田七, 田七]

        Set<Integer> set = employees.stream()
                .map(e -> e.getAge())
                .collect(Collectors.toSet());
        System.out.println(set);//输出结果为:[21, 56, 25, 30]

        HashSet<String> hashSet = employees.stream()
                .map(e -> e.getName())
                .collect(Collectors.toCollection(HashSet::new));
        System.out.println(hashSet);//输出结果为:[李四, 张三, 王五, 田七]

    }

因为collect收集使用是很常见的,接下来我们通过使用collect进行统计、求平均值、总和、最大值、最小值,更加熟悉collect的使用,并了解工具类Collectors中常用的方法

    @Test
    public void test15(){
        //总数
        Long count = employees.stream()
                .collect(Collectors.counting());
        System.out.println(count); // 输出结果为:6

        //获取员工薪资的平均值
        Double avgSalary = employees.stream()
                .collect(Collectors.averagingDouble(Employee::getSalary));
        System.out.println(avgSalary);// 输出结果为:5583.333333333333

        //获取员工薪资的总和
        Double total = employees.stream()
                .collect(Collectors.summingDouble(Employee::getSalary));
        System.out.println(total); // 输出结果为:33500.0

        //获取最高薪资的员工信息
        Optional<Employee> maxSalary = employees.stream()
                .collect(Collectors.maxBy((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary())));
        System.out.println(maxSalary.get()); //输出结果为:Employee{name='田七', age=30, salary=8000.0, status=FREE}

        //获取最低薪资的员工信息
        Optional<Employee> minSalary = employees.stream()
                .collect(Collectors.minBy((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary())));
        System.out.println(minSalary.get()); //输出结果为:Employee{name='王五', age=56, salary=3500.0, status=BUSY}
    }

通过使用collect,对流中元素进行分组、多级分组、分区操作。

    @Test
    public void test16(){
        //通过员工状态进行分组
        Map<Employee.Status, List<Employee>> statusListMap = employees.stream()
                .collect(Collectors.groupingBy(Employee::getStatus));
        System.out.println(statusListMap);
    }

    /**
     * 多级分组
     */
    @Test
    public void test17(){
        //通过员工状态进行分组
        Map<Employee.Status, Map<String, List<Employee>>> map = employees.stream()
                .collect(Collectors.groupingBy(Employee::getStatus, Collectors.groupingBy(e -> {
                    if (e.getAge() < 30) {
                        return "青年";
                    } else if (e.getAge() < 50) {
                        return "中年";
                    } else {
                        return "老年";
                    }
                })));
        System.out.println(map);
    }

    /**
     * 分区
     */
    @Test
    public void test18(){
        Map<Boolean, List<Employee>> map = employees.stream()
                .collect(Collectors.partitioningBy(e -> e.getSalary() > 6000));
        System.out.println(map);
    }

    /**
     * 将流中的元素,按照指定格式连接
     */
    @Test
    public void test19(){
        String str = employees.stream()
                .map(e -> e.getName())
                .collect(Collectors.joining(","));
        System.out.println(str); //输出结果为: 张三,李四,王五,王五,田七,田七
    }

并行流与顺序流

并行流:并行流就是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流。 Java8中将并行流进行了优化,我们可以很容易地对数据进行并行操作。Stream API可以声明性地通过Parallel()与sequential()在并行流与顺序流之间进行切换。 以下实例我们使用 parallelStream 来输出空字符串的数量:

    @Test
    public void test20(){
        List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
        // 获取空字符串的数量
        long count = strings.parallelStream().filter(string -> string.isEmpty()).count();
        System.out.println(count); //输出结果为:2
    }

Stream API应用

Java8中的Stream API可以极大提高我们的的生产力,让我们写出高效率、干净、简洁的代码。 例如:使用Java8来求两个集合的交集、差集、并集

    @Test
    public void test(){

        //准备两个集合
        List<String> list1 = new ArrayList<String>();
        list1.add("aa");
        list1.add("bb");
        list1.add("cc");
        list1.add("dd");
        list1.add("ee");

        List<String> list2 = new ArrayList<String>();
        list2.add("bb");
        list2.add("cc");
        list2.add("ff");
        list2.add("gg");

        // 交集
        List<String> intersection = list1.stream().filter(item -> list2.contains(item)).collect(toList());
        System.out.println("---交集 intersection---");
        intersection.parallelStream().forEach(System.out :: println);

        // 差集
        List<String> reduce = list2.stream().filter(item -> !list1.contains(item)).collect(toList());
        System.out.println("---差集 reduce2 (list2 - list1)---");
        reduce.parallelStream().forEach(System.out :: println);

        //并集
        list1.addAll(list2);
        List<String> collect = list1.stream().distinct().collect(toList());
        System.out.println("并集----去重");
        collect.stream().forEach(System.out::println);
    }