jdk8-stream

后端 / 笔记 / 2021-09-19

什么是stream

Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。

image.png

创建stream

创建stream共5种方式

  • 通过数组创建
  • 通过列表创建
  • Stream.generate()
  • Stream.iterate()
  • Stream.of()

通过数组创建

// 通过数组创建
int[] arr = new int[]{1,2,3,4,5,6,7,8};
Arrays.stream(arr).filter(i->i%2==0).forEach(System.out::println);

通过列表创建

// 通过 list 创建
Arrays.asList(1,2,3,4,5,6,7,8).stream().filter(s->s%2==0).forEach(System.out::println);

Stream.generate()

会生成一个无限的stream,通常和 limit方法配合使用

// Stream.generate()
Stream.generate(()->1).limit(5).forEach(System.out::println);

Stream.iterate()

会生成一个无限的 stream,第一个参数为首项,也是通常和 limit方法配合使用

// Stream.iterate()
Stream.iterate(0,i->i+1).limit(5).forEach(System.out::println);

Stream.of()

//Stream.of()
Stream.of(1,2,3,4,5,6).limit(3).forEach(System.out::println);

通过stream将list转换为set

package jdk8.stream;

import java.util.ArrayList;
import java.util.Set;
import java.util.stream.Collectors;

/**
* 员工类 name,age,job
*/
class Employee{
    private String name;
    private Integer age;
    private String job;

    Employee(String name, Integer age, String job) {
        this.name = name;
        this.age = age;
        this.job = job;
    }


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



public class CreateStreamTest {
    public static void main(String[] args) {

        ArrayList<Employee> employees = new ArrayList<>();

        employees.add(new Employee("luckyFang",22,"全栈工程师"));
        employees.add(new Employee("alice",20,"程序员鼓励师"));
        employees.add(new Employee("zhangsan",35,"安保队长"));
        employees.add(new Employee("zhangsan",35,"安保队长"));


        /**
         * 创建流:
         *  - 串行流 单线程
         *  - 并行流 多线程 (有效率优势)
         */

        // 串行流
        employees.stream();
        // 并行流
        employees.parallelStream();


        // 将 list 转换为 set集合
        Set<Employee> collect = employees.stream().collect(Collectors.toSet());

        // 遍历 set
        collect.forEach(ele-> System.out.println(ele));


        /**
         * 思考问题: set 特性是 无序不重复集合
         *  但是查看输出结果发现了重复了 可见 我们转set失败了
         *  为什么会失败?
         */


    }
}

Employee{name='zhangsan', age=35, job='安保队长'}
Employee{name='luckyFang', age=22, job='全栈工程师'}
Employee{name='alice', age=20, job='程序员鼓励师'}
Employee{name='zhangsan', age=35, job='安保队长'}

思考问题: set 特性是 无序不重复集合
但是查看输出结果发现了重复了 可见 我们转set失败了
为什么会失败?


这里就涉及到java基础知识了,虽然对象属性是相等的,但是我们每次new对象的地址是不一样的,所以我们需要重写equals方法,和hashCdoe方法。

为什么需要这样做?

如果集合要添加元素时,会先调用这个元素的hashCode方法,来决定要存储的位置,如果这个位置没有元素则直接存储,如果有元素,则需要调用equals方法来判断是否是同一个对象,如果相同则不存,不相同就散列它的地址.

  • 如果两个java对象相同,那么它的hashCode一定相同。
  • 如果两个对象的hashCode相同那么他们并不一定相同
class Employee{
    private String name;
    private Integer age;
    private String job;

    Employee(String name, Integer age, String job) {
        this.name = name;
        this.age = age;
        this.job = job;
    }


    @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(job, employee.job);
    }

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

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

然后就解决了对象重复问题.

通过stream将list转换为map

public static void listToMap(){
  ArrayList<Employee> employees = new ArrayList<>();
  employees.add(new Employee("luckyFang",22,"全栈工程师"));
  employees.add(new Employee("alice",20,"程序员鼓励师"));
  employees.add(new Employee("zhangsan",35,"安保队长"));

  /**
  * Fun1:
  *  param1: key
  * Fun2:
  *  param2: val
  */
  employees.stream().collect(Collectors.toMap(new Function<Employee, String>() {
    @Override
    public String apply(Employee employee) {
      return employee.getName();
    }
  }, new Function<Employee, Employee>() {
    @Override
    public Employee apply(Employee employee) {
      return employee;
    }
  })).forEach(new BiConsumer<String, Employee>() {
    @Override
    public void accept(String s, Employee employee) {
      System.out.printf("%s:%s\n",s,employee);
    }
  });
}

reduce实现求和

这里通过reduce操作实现求和,需要注意的是返回值类型要和列表类型一致。
reduce: 通过计算模型将stream中所有的值计算最终结果

public static void streamReduceTest() {
  // 创建stream
  Stream<Integer> integerStream = Stream.of(10, 50, 30, 10);
  // reduce:通过计算模型将stream中的值计算为一个最终的结果
  Optional<Integer> reduce = integerStream.reduce((integer, integer2) -> integer + integer2);
  // Optional包装对象 通过.get() 访问
  System.out.println(reduce.get());


  // 员工类 年龄相加
  ArrayList<Employee> employees = new ArrayList<>();
  employees.add(new Employee("luckyFang", 22, "全栈工程师"));
  employees.add(new Employee("alice", 20, "程序员鼓励师"));
  employees.add(new Employee("zhangsan", 35, "安保队长"));


  // 通过stream 将所有员工姓名相加
  Optional<Employee> ageSum = employees.stream().reduce((e1, e2) -> new Employee("ageSum", e1.getAge() + e2.getAge(), "null"));
  System.out.println(ageSum.get());

}
Employee{name='ageSum', age=77, job='null'}

max和min找最大最小值

public static void streamMaxOrMInTest() {
  ArrayList<Employee> employees = new ArrayList<>();
  employees.add(new Employee("luckyFang", 22, "全栈工程师"));
  employees.add(new Employee("alice", 20, "程序员鼓励师"));
  employees.add(new Employee("zhangsan", 35, "安保队长"));

  // 找到所有员工中年龄最大的
  Optional<Employee> max = employees.stream().max((o1, o2) -> o1.getAge() - o2.getAge());
  System.out.println(max.get());

  // 找到所有员工中年龄最小的

  Optional<Employee> min = employees.stream().min((o1, o2) -> o1.getAge() - o2.getAge());
  System.out.println(min.get());
}
Employee{name='zhangsan', age=35, job='安保队长'}
Employee{name='alice', age=20, job='程序员鼓励师'}

match实现条件筛选

anyMatch: 任一匹配
allMatch: 全部匹配

public static void matchTest(){
  ArrayList<Employee> employees = new ArrayList<>();
  employees.add(new Employee("luckyFang", 22, "全栈工程师"));
  employees.add(new Employee("alice", 20, "程序员鼓励师"));
  employees.add(new Employee("zhangsan", 35, "安保队长"));

  // anyMatch 任意匹配
  // 查看员工中有没有小于18岁的
  System.out.println(employees.stream().anyMatch(new Predicate<Employee>() {
    @Override
    public boolean test(Employee employee) {
      return employee.getAge() < 18;
    }
  }));


  // allMatch 全部匹配
  // 查看所有员工是不是大于18岁
  System.out.println(employees.stream().allMatch(new Predicate<Employee>() {
    @Override
    public boolean test(Employee employee) {
      return employee.getAge() > 18;
    }
  }));

}
false
true

filter过滤器

filter:过滤器,通过断言决定取舍,满足条件的则保留,不满足条件的抛去.

public static void filterTest() {
  ArrayList<Employee> employees = new ArrayList<>();
  employees.add(new Employee("luckyFang", 22, "全栈工程师"));
  employees.add(new Employee("alice", 20, "程序员鼓励师"));
  employees.add(new Employee("zhangsan", 35, "安保队长"));

  // 查找所有员工中年龄小于 30岁的
  employees.stream().filter(new Predicate<Employee>() {
    @Override
    public boolean test(Employee employee) {
      return employee.getAge() < 30;
    }
  }).forEach(System.out::println);
}
Employee{name='luckyFang', age=22, job='全栈工程师'}
Employee{name='alice', age=20, job='程序员鼓励师'}

limit和skip

limit:最大限制(几条数据)
skip: 跳过(第几条数据)
注意:初始下标从1开始

    public static void limitTest() {
        System.out.println("===========");
        ArrayList<Employee> employees = new ArrayList<>();
        employees.add(new Employee("luckyFang", 22, "全栈工程师"));
        employees.add(new Employee("alice", 20, "程序员鼓励师"));
        employees.add(new Employee("zhangsan", 35, "安保队长"));

        // 代表从头到尾取两条数据
        employees.stream().limit(2).forEach(System.out::println);

        // skip 跳过
        System.out.println("======");
        // 跳过第一条数据 后取两条数据
        employees.stream().skip(1).limit(2).forEach(System.out::println);
    }
}
======
Employee{name='luckyFang', age=22, job='全栈工程师'}
Employee{name='alice', age=20, job='程序员鼓励师'}
======
Employee{name='alice', age=20, job='程序员鼓励师'}
Employee{name='zhangsan', age=35, job='安保队长'}

sorted实现排序

public static void sortTest(){
  System.out.println("===========");
  ArrayList<Employee> employees = new ArrayList<>();
  employees.add(new Employee("luckyFang", 22, "全栈工程师"));
  employees.add(new Employee("alice", 20, "程序员鼓励师"));
  employees.add(new Employee("zhangsan", 35, "安保队长"));
  // 实现排序
  employees.stream().sorted((o1, o2) -> o1.getAge()-o2.getAge()).forEach(System.out::println);
}
Employee{name='alice', age=20, job='程序员鼓励师'}
Employee{name='luckyFang', age=22, job='全栈工程师'}
Employee{name='zhangsan', age=35, job='安保队长'}

综合实战

public static void demoTest() {
  System.out.println("\n\n\n");
  ArrayList<Employee> employees = new ArrayList<>();
  employees.add(new Employee("zhangsan", 30, "后端工程师"));
  employees.add(new Employee("lisi", 27, "嵌入式工程师"));
  employees.add(new Employee("wangwu", 21, "实习生"));
  employees.add(new Employee("zhaoliu", 37, "架构师"));

  // 说明 按照年龄降序排列所有工程师
  employees.stream().sorted((o1, o2) -> o2.getAge() - o1.getAge()).filter(o -> o.getJob().contains("工程师")).forEach(System.out::println);
}

Employee{name='zhangsan', age=30, job='后端工程师'}
Employee{name='lisi', age=27, job='嵌入式工程师'}

并行流

分开任务去执行

public static void parallelTest() {

  // 串行流求和

  Instant now1 = Instant.now();
  long sum = 0;
  for (long i = 0; i < 500000000L; i++) {
    sum += i;
  }
  System.out.println("正常求和:" + Duration.between(now1, Instant.now()).toMillis());

  // 并行流求和
  Instant now = Instant.now();
  // 50亿数据
  LongStream longStream = LongStream.rangeClosed(0, 500000000L);
  OptionalLong reduce = longStream.parallel().reduce((l, r) -> l + r);
  System.out.println("并行求和:" + Duration.between(now, Instant.now()).toMillis());
}
正常求和:340
并行求和:199