系统学习Java新特性-Stream API使用

403 阅读4分钟

这是我参与11月更文挑战的第3天,活动详情查看:2021最后一次更文挑战

阅读《Java实战》,本文为第5章总结,主要系统全面的介绍Steam的API,包含筛选、切片和映射,查找与匹配、归约操作、数值流、以及流的各种构建方法。

1、筛选

从流中筛选出目标对象,主要分为两个:

  • filter(Predicate):基于谓词(返回boolean的函数式接口)筛选过滤
  • distinct() : 筛选去重
  • limit(n) :截断流,即返回一个不超过指定长度n的流
  • skip(n):跳过前面n个元素,和limit互补,实现类似分页功能

使用示例:

    // filter示例:过滤出素食的选项
    List<Dish> vegetarianMenu =
            menu.stream()
                    .filter((m) -> m.isVegetarian())// lambda实现
                    //.filter(Dish::isVegetarian) // 方法引用
                    .collect(Collectors.toList());
    
    vegetarianMenu.forEach(
           // (Dish d) -> System.out.println(d) //lambda实现
            System.out::println
    );
    
    // distinct示例:筛选出偶数并去重复
    List<Integer> intArr = Arrays.asList(1,5,4,2,3,3,2,4,6);
    //输出:426
    intArr.stream()
            .filter(i -> i%2==0)
            .distinct()
            .forEach(System.out::print);
    // limit示例:
    // 输出:42
    intArr.stream()
            .filter(integer -> integer%2==0)
            .limit(2)
            .forEach(System.out::print);
    // skip示例:
    // 输出:26
    intArr.stream()
            .filter(integer -> integer%2==0)
            .distinct()
            .skip(1)
            .forEach(System.out::print);

2、映射

将每个元素映射处理成一个新的元素,主要实现有:

  • map(Function<T,R> f):将函数应用到每个元素上,并将其转换成一个新的元素。
  • mapToInt(ToIntFunction f):接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的IntSteam。
  • flatMap(Function f):接收一个函数作为参数,将流中的每个值都转换成另外一个流,然后把所有流连接成一个流。
    //map示例:Dish对象映射成dishName
    List<String> dishNames =
            Dish.menu.stream()
                    .map(
                        //(Dish dish) -> dish.getName() //基于Lambda
                        Dish::getName // 基于方法引用
                    ).collect(Collectors.toList());
    // [pork, beef, chicken, french fries, rice, season fruit, pizza, prawns, salmon]
    System.out.println(dishNames);

    //map的复合示例:除了映射成dishName的字符长度
    List<Integer> dishNameLengths =
            Dish.menu.stream()
                    .map(Dish::getName)
                    .map(String::length)
                    .collect(Collectors.toList());
    // [4, 4, 7, 12, 4, 12, 5, 6, 6]
    System.out.println(dishNameLengths);

    //flatMap示例:将单词数组,按字母整体去重处理
    String[] helloWorld = {"Hello","World"};
    List<String> uniqueCharacters =
            Arrays.stream(helloWorld)
                    .map(word -> word.split(""))//Stream<String[]> 拆分成一个个字符的数组流
                    .flatMap(Arrays::stream)//将所有数组流汇聚成一个新流
                    .distinct()
                    .collect(Collectors.toList());
    // [H, e, l, o, W, r, d]
    System.out.println(uniqueCharacters);

3、查找与匹配

查找或判断数据流中某些元素是否匹配一个给定的属性,常见API如下:

  • anyMatch(Predicate p):至少有一个元素匹配给定的谓词
  • allMatch(Predicate p):检查是否匹配所有元素
  • noneMatch(Predicate p):检查是否没有匹配的元素
  • findAny():返回当前流中的任意元素(注意,返回是Optional对象)
  • findFirst():返回第一个匹配的元素(和findAny区别是并发流上)
    //anyMatch: 是否存在素食
    System.out.println(
            Dish.menu.stream()
                    .anyMatch(Dish::isVegetarian)
    );//true

    //allMatch:所有菜热量是否<1000
    System.out.println(
        Dish.menu.stream().allMatch(dish -> dish.getCalories()<1000)
    );//true

    // findAny : 返回一道素食
    Optional<Dish> dish =
            Dish.menu.stream()
                    .filter(Dish::isVegetarian)
                    .findAny();

    //french fries#true
    dish.ifPresent(dish1 ->
            System.out.println(dish1.getName()+"#"+dish1.isVegetarian()));

4、归约操作

将流中的元素反复结合起来,从而得到一个值,比如最大值、热量总和,这样的操作统称为归约操作

    List<Integer> intList = Arrays.asList(1,3,5,2,4,6);
    // 求和计算
    Integer sum = intList.stream().reduce(0,(a,b) -> a+b);
    Integer sum1 = intList.stream().reduce(0,Integer::sum);
    System.out.println(sum+"#"+sum1);//21#21
    //无初始值,返回Optional<T>
    Optional<Integer> sum2 = intList.stream().reduce(Integer::sum);
    sum2.ifPresent(System.out::println);//21

    // 求最值示例:
    Optional<Integer> maxOpt = intList.stream().reduce((x,y) -> x>y?x:y);
    Optional<Integer> maxOpt1 = intList.stream().reduce(Integer::max);
    System.out.println(maxOpt.get()+"#"+maxOpt1.get());//6#6

    intList.stream().reduce(Integer::min).ifPresent(System.out::println);//1

5、数值流

这块主要基于数值特点,有两个特殊场景,规避数值类型的装箱问题的基础类型流特化数值范围两个。

5.1 基础类型流特化

使用reduce对Integer类似对象进行数值计算暗含装拆箱成本,因此StreamAPI提供了IntStreamDoubleStreamLongStream三个基础类型流特化接口来处理,避免了潜在的装箱成本。

    // 映射成数值流
    IntStream intStream = Dish.menu.stream()
            .mapToInt(Dish::getCalories);
    // 结果返回的是OptionalInt
    OptionalInt optionalInt = intStream.max();
    //orElse值不存在时默认值
    System.out.println(optionalInt.orElse(-999));
    System.out.println("----------");
    // 装箱转换成Stream
    intStream = Dish.menu.stream()
            .mapToInt(Dish::getCalories);
    Stream<Integer> integerStream = intStream.boxed();
    integerStream.forEach(System.out::println);

5.2 数值范围

数值流针对生产指定范围数字的需求,提供了rangerangeClosed两个方法,有两个参数,指定范围,后面方法为闭区间。

    // 数值范围方法
    IntStream evenNumbers = IntStream.rangeClosed(1, 10)
            .filter(n -> n % 2 == 0);
    evenNumbers.forEach(i -> System.out.print(i+","));//2,4,6,8,10,

6、构建流

数据元素源时Stream数据处理操作的基石,本小节主要系统介绍除了Collection外所有可能的流生成方式。

  • 由值创建流:利用Stream.of()可以直接将值对象封装成Stream
  • 数组创建:使用Arrays.stream()静态方法,将数组直接转换成流
  • 文件生成流:主要利用java.nio.file.Files工具类来实现,比如lines()方法
  • 由函数生成流:主要时使用Stream.iterate(final T seed, final UnaryOperator<T> f)Stream.generate()方法来循环调用函数生成无限流。
    // 基于值序列创建
    Stream<String> stream = Stream.of("Java","In","Action");
    stream.map(String::toUpperCase).forEach(System.out::println);

    // 基于数组创建
    int[] intArr = {1,3,5,2,4,6};
    int sum = Arrays.stream(intArr).sum();
    System.out.println(sum);

    // 基于文件创建
    Path path = Paths.get(BuildingStreams.class.
            getClassLoader().getResource("data.txt").toURI());
    try(Stream<String> lineStream = Files.lines(path)){
        lineStream.forEach(System.out::println);
    }

    // 基于函数生成:因为无限流,所以配合limit使用
    Stream.iterate(0,n -> n+2)
            .limit(5)
            .forEach(e -> System.out.print(e+","));//0,2,4,6,8,

    Stream.generate(Math::random)
            .limit(5)
            .forEach(e -> System.out.print(e+","));//随机生成5个随机数

本文涉及的代码示例地址