Java8 Stream API

270 阅读5分钟

一、基本概念

  • Stream 是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作
  • “集合讲的是数据,流讲的是计算!”
  • ①Stream 自己不会存储元素。 ②Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream。 ③Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。
  • Stream 的操作三个步骤
    • 创建 Stream:一个数据源(如:集合、数组),获取一个流
    • 中间操作:一个中间操作链,对数据源的数据进行处理
    • 终止操作:一个终止操作,执行中间操作链,并产生结果

二、创建Stream

  • Collection接口,从Java8开始拓展了两个获取流的方法:
    • default Stream<E> stream() : 返回一个顺序流
    • default Stream<E> parallelStream() : 返回一个并行流
      @Test
      public void test() {
        List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5);
        Stream<Integer> stream = integers.stream();
        stream.forEach(System.out::println);
    
        Stream<Integer> parallelStream = integers.parallelStream();
        parallelStream.forEach(System.out::println);  // 输出并不会按顺序哦!
      }
    
  • Arrays的静态方法stream(),可以获取数组流
      @Test
      public void test2() {
        int[] ints = {1, 2, 3, 4};
        IntStream stream = Arrays.stream(ints);
        stream.forEach(System.out::println);
    
        double[] doubles = {1.0, 2.0, 3.0, 4.0};
        DoubleStream doubleStream = Arrays.stream(doubles);
        doubleStream.forEach(System.out::println);
      }
    
  • Stream的静态方法of()创建一个流
      @Test
      public void test3() {
        Stream<Integer> stream = Stream.of(1, 2, 3);  // 内部还是调用Arrays.stream()方法
        stream.forEach(System.out::println);
      }
    
  • 创建无限流:可以由静态方法Stream.iterate()Stream.generate(), 创建无限流。
      @Test
      public void test4() {
        Stream<Integer> stream = Stream.iterate(0, (x) -> x + 2);
        stream.limit(10).forEach(System.out::println);
      }
    
      @Test
      public void test5() {
        Stream<Integer> stream = Stream.generate(() -> 1);
        stream.limit(10).forEach(System.out::println);
      }
    

    Stream.iterate()需要传进去一个seed作为起始值,一个UnaryOperator接口(Function的子接口)输入 Stream.generate()需要传进去一个supplier接口

三、Stream的中间操作

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

3.1 筛选与切片

  • filter过滤不合适的,需要输入一个Predicate的函数时接口
      @Test
      public void test6() {
        List<Employee> employees = new ArrayList<>();
        employees.add(new Employee(0, new BigDecimal(10000), "Amy"));
        employees.add(new Employee(1, new BigDecimal(9000), "John"));
        employees.add(new Employee(3, new BigDecimal(8000), "Jerry"));
        employees.add(new Employee(2, new BigDecimal(7000), "Taylor"));
    
        employees.stream()
                .filter((e) -> e.getSalary().compareTo(new BigDecimal(8000)) > 0)
                .forEach(System.out::println);
      }
    
  • distinct去重
      @Test
      public void test7() {
        List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5, 1, 2, 3, 2, 6, 7, 8);
        Stream<Integer> stream = integers.stream();
        stream.distinct().forEach(System.out::println);
      }
    

    如果对对象去重,需要让对象的类实现hasCode()equals方法,否则底层会比较地址,然后发现都不一样,没办法去重

  • limit截断流,限制元素不超过指定数量
      @Test
      public void test5() {
        Stream<Integer> stream = Stream.generate(() -> 1);
        stream.limit(10).forEach(System.out::println);
      }
    
  • skip跳过前n个元素
      @Test
      public void test9() {
        List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
        Stream<Integer> stream = integers.stream();
        stream.skip(3).forEach(System.out::println);  // 跳过前三个
      }
    

3.2 映射

  • map 接收一个函数作为参数,该函数会应用到每个元素上,并将其映射成一个新的元素
      @Test
      public void test10() {
        List<Employee> employees = new ArrayList<>();
        employees.add(new Employee(0, new BigDecimal(10000), "Amy"));
        employees.add(new Employee(1, new BigDecimal(9000), "John"));
        employees.add(new Employee(3, new BigDecimal(8000), "Jerry"));
        employees.add(new Employee(2, new BigDecimal(7000), "Taylor"));
    
        employees.stream()
                .map(Employee::getName)   // 类名::方法名
                .forEach(System.out::println);
      }
    
  • mapToInt 接收一个函数作为参数,该函数会应用到每个元素上,产生一个新的IntStream
      @Test
      public void test11() {
        List<Employee> employees = new ArrayList<>();
        employees.add(new Employee(0, new BigDecimal(10000), "Amy"));
        employees.add(new Employee(1, new BigDecimal(9000), "John"));
        employees.add(new Employee(3, new BigDecimal(8000), "Jerry"));
        employees.add(new Employee(2, new BigDecimal(7000), "Taylor"));
    
        employees.stream()
                .mapToInt(Employee::getId)   // 类名::方法名
                .forEach(System.out::println);
      }
    
  • flatMap 就是套娃,可以在方法里面再套map,可以将多维数组摊平
      @Test
      public void test12() {
        Employee[][] employees = {
                {new Employee(0, new BigDecimal(10000), "Amy"), new Employee(1, new BigDecimal(9000), "John")},
                {new Employee(3, new BigDecimal(8000), "Jerry")},
                {new Employee(2, new BigDecimal(7000), "Taylor"), new Employee(4, new BigDecimal(6000), "Swift")}
        };
    
        Arrays.stream(employees).flatMap(Arrays::stream).forEach(System.out::println);  // 针对每个一维数组,也通过Arrays.stream进行转换
        Arrays.stream(employees)
                .flatMap((es) -> Arrays.stream(es).map(Employee::getName))  // 针对每个一维数组,再转换成stream,并使用映射
                .forEach(System.out::println);
      }
    

3.3 排序

  • sorted 产生一个新流,可以按自然排序,也可以按自己定制的方法排序(需要传入Comparator或类自己已经实现了Comparable
      @Test
      public void test13() {
        List<Employee> employees = new ArrayList<>();
        employees.add(new Employee(0, new BigDecimal(10000), "Amy"));
        employees.add(new Employee(1, new BigDecimal(9000), "John"));
        employees.add(new Employee(3, new BigDecimal(8000), "Jerry"));
        employees.add(new Employee(2, new BigDecimal(7000), "Taylor"));
        // Comparator.comparing(Employee::getSalary)表示获取一个通过salary排序的比较器
        Stream<Employee> sorted = employees.stream().sorted(Comparator.comparing(Employee::getSalary));
        //Stream<Employee> sorted = employees.stream().sorted((o1, o2) -> o1.getSalary().compareTo(o2.getSalary()));  // 前后等效
        sorted.forEach(System.out::println);
      }
    
      @Test
      public void test14() {
        List<Integer> integers = Arrays.asList(4, 58, 6, 4, 33, -8, -7, -10);
        integers.stream().sorted().forEach(System.out::println);   // 使用自然排序的条件是比较的类型是基本数据类型或者是已经实现了Comparable接口
      }
    

四、Stream 的终止操作

4.1 查找与匹配

  • allMatch 检查是否匹配所有元素,需要传进去一个断言Predicate接口
    List<Integer> integers = Arrays.asList(4, 58, 6, 4, 33, -8, -7, -10);
    Stream<Integer> stream = integers.stream();
    
    boolean res = stream.allMatch((x) -> x > 0);
    System.out.println(res);  // false
    
  • anyMatch 检查是否至少匹配一个元素
    List<Integer> integers = Arrays.asList(4, 58, 6, 4, 33, -8, -7, -10);
    Stream<Integer> stream = integers.stream();
    
    res = stream.anyMatch((x) -> x > 0);  // true
    System.out.println(res);
    
  • noneMatch 检查是否都不匹配
    List<Integer> integers = Arrays.asList(4, 58, 6, 4, 33, -8, -7, -10);
    Stream<Integer> stream = integers.stream();
    
    res = stream.noneMatch((x) -> x <= 0);  //false
    System.out.println(res);
    
  • findFirst 返回第一个元素
    List<Integer> integers = Arrays.asList(4, 58, 6, 4, 33, -8, -7, -10);
    Stream<Integer> stream = integers.stream();
    
    Optional<Integer> first = stream.sorted().findFirst();  // 获取第一个
    Integer integer;
    if (first.isPresent()){
      integer = first.get();
      System.out.println(integer);  // -10
    }
    
  • findAny 返回任意一个元素
    List<Integer> integers = Arrays.asList(4, 58, 6, 4, 33, -8, -7, -10);
    Stream<Integer> stream = integers.stream();
    
    Optional<Integer> any = stream.findAny();   // 随便选一个,但不随机
    if (any.isPresent()) {
      integer = any.get();
      System.out.println(integer);
    }
    
  • count 返回流中元素总数
    List<Integer> integers = Arrays.asList(4, 58, 6, 4, 33, -8, -7, -10);
    Stream<Integer> stream = integers.stream();
    
    long count = stream.count();  // 计数
    System.out.println(count);
    
  • max 返沪流中最大值
    List<Integer> integers = Arrays.asList(4, 58, 6, 4, 33, -8, -7, -10);
    Stream<Integer> stream = integers.stream();
    
    Optional<Integer> max = stream.max(Integer::compareTo);  // 求最大值
    if (max.isPresent()) {
      integer = max.get();
      System.out.println(integer);
    }
    
  • min 返回流中最小值
    List<Integer> integers = Arrays.asList(4, 58, 6, 4, 33, -8, -7, -10);
    Stream<Integer> stream = integers.stream();
    
    Optional<Integer> min = stream.min(Integer::compareTo);  // 求最小值
    if (min.isPresent()) {
      integer = min.get();
      System.out.println(integer);
    }    
    
  • forEach 内部迭代,见上

4.2 归约

  • reduce 将流中元素反复结合起来,得到一个值
    List<Integer> integers = Arrays.asList(4, 58, 6, 4, 33, -8, -7, -10);
    Stream<Integer> stream = integers.stream();
    
    Optional<Integer> reduce = stream.reduce(Integer::sum);  // 可以将流中元素反复结合起来,得到一个值。求和!
    if (reduce.isPresent()) {
      integer = reduce.get();
      System.out.println(integer);
    }
    

4.3 收集

  • collect 将流转换成其他形式。通过接收一个Collector接口来实现,用来给流中的元素做summary
  • 一般通过Collectors实用类获取Collector
    List<Integer> integers = Arrays.asList(4, 58, 6, 4, 33, -8, -7, -10);
    Stream<Integer> stream = integers.stream();
    List<Integer> collect = stream.collect(Collectors.toList());
    System.out.println(collect);
    
    stream = integers.stream();
    Set<Integer> collect1 = stream.collect(Collectors.toSet());
    System.out.println(collect1);
    
    List<String> strings = Arrays.asList("1", "2", "3");
    Stream<String> stream1 = strings.stream();
    String collect2 = stream1.collect(Collectors.joining());  // 将String合并成一个字符串,默认通过空格分隔,可以输入分隔符
    System.out.println(collect2);
    
  • 具体方法及实例 在这里插入图片描述 在这里插入图片描述

五、并行流与串行流

  • 并行流就是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流。
  • Stream API 可以声明性地通过 parallel()sequential() 在并行流与顺序流之间进行切换。
      @Test
      public void test16() {
        List<Integer> list = new ArrayList<>();
        for (int i = 0; i < 100000000; i++) {
          list.add(i);
        }
    
        Instant start = Instant.now();
        int sum = list.stream().parallel().mapToInt(x -> x).sum();
        //sum = list.parallelStream().mapToInt(x -> x).sum();
        Instant end = Instant.now();
        System.out.println(sum);
        System.out.println("花费时间" + Duration.between(end, start));
      }