当前位置 博文首页 > 龙台的技术笔记:Java8 Stream写给自己的小白流式操作

    龙台的技术笔记:Java8 Stream写给自己的小白流式操作

    作者:[db:作者] 时间:2021-08-04 15:05

    在这里插入图片描述

    Java8 的出现可以说是 Java 面向函数式编程前进的一大步。刨除了很多冗余操作,间接的解放了双手👋

    在看本篇文章时如果对 Java8的函数式接口不了解可以移步 Java8 Functional Interface写给自己的小白函数式接口说明


    Java8 Stream 采用的是函数式编程方式,使用函数式编程方式在集合类上进行复杂操作的工具,可以更容易的使用 Lambda 表达式的形式书写,就看下流式操作正确的打开方式

    对于不太清楚Stream流式操作的朋友而言,可以尽情 XX 了 😁
    另附一张归结的思维导图,可以查看是否有自己需要的 API

    在这里插入图片描述

    1 流的创建

    创建流的方式有很多种,比如:Arrays.streamStream.ofCollection.streamStream.iterateStream.generate,流的创建方式有很多,比较常用的则是根据 Collection.stream(),从集合中创建流

    static <T> Stream<T> stream(T[] array)
        
    static<T> Stream<T> of(T... values)
        
    default Stream<E> stream()
        
    static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)
        
    static<T> Stream<T> generate(Supplier<T> s)
    

    第一种stream方法是 Arrays.stream | 第三种是 Collection.stream


    1.1 创建流方式示例

    Arrays.stream 通过 Arrays 的静态方法来进行流的创建,参数为一个范型数组

    String[] strArray = { "龙台", "百凯", "英雄" };
    Arrays.stream(strArray).forEach(System.out::println);
    // 顺序输出  龙台  百凯  英雄
    

    Stream.of 传入一个泛型数组,或者多个参数,创建一个流,底层也是调用了 Arraysstream 静态方法

    String[] strArray = { "龙台", "百凯", "英雄" };
    Stream.of(strArray).forEach(System.out::println);
    // 顺序输出  龙台  百凯  英雄
    

    Collection.stream 可以使用集合的接口的默认方法。使用这个方法,包括继承 Collection 的接口,如:Set,List,Map,SortedSet 等等都可以创建流

    List<String> strList = Arrays.asList("龙台", "百凯","英雄");
    strList.stream().forEach(System.out::println);
    // 顺序输出  龙台  百凯  英雄
    

    Stream.iterate 该方式是在 Stream 接口下的静态方法,见名识义可以看出是以迭代器的方式创建一个流

    Stream.iterate(1, each -> each + 1)
            .limit(3)
            .collect(Collectors.toList())
            .forEach(System.out::println);
    // 顺序输出 1 2 3
    

    Stream.generate 同样是 Stream 接口下的静态方法,参数就是一个 Supplier 的供给型的参数

    Stream.generate(Math::random)
            .limit(5)
            .forEach(System.out::println);
    // 依次打印5个随机数
    

    2 Stream 流的处理顺序

    Stream 流的系列流程可以总结分为以下操作

    数据源(source) -> 数据转换/处理(中间操作) -> 结果处理(终端操作)
    数据源指的就是通过以上几种方式生成的 Stream 流。需要注意的是:终端操作一旦执行,那么整个流的生命周期就结束了,无法再进行计算或再次汇总


    ??????之前我也认为,会顺序输出 龙台 百凯两个字符串,但是实际上是一个也不会输出,因为当前代码中 没有包含终端操作

    Stream.of("龙台", "百凯")
            .filter(each -> {
                System.out.println(each);
                return true;
            });
    

    如果说在👆代码加以点缀,那么就变得不一样了

    Stream.of("龙台", "百凯")
            .filter(each -> {
                System.out.println("filter:" + each);
                return true;
            })
            .forEach(each -> System.out.println("forEach:" + each));
        }
    filter:龙台
    forEach:龙台
    filter:百凯
    forEach:百凯
    

    之前一直认为是执行完 filter 操作,然后才会执行接下来的 forEach 👋👋👋的打脸


    Stream 包含中间操作,这个在使用上也是一个坑,那就是流的 延迟性,也叫做 惰性操作
    实际上在处理 龙台 元素的时候,执行完 filter 就会执行 forEach ,然后继续执行 百凯 元素

    查阅资料说明 这种设计的方式是出于性能考虑

    Stream.of("龙台", "百凯", "英雄")
            .map(each -> {
                System.out.println("map: " + each);
                return each;
            })
            .anyMatch(each -> {
                System.out.println("anyMatch: " + each);
                return each.startsWith("百"); // 过滤以 '百' 开头的元素
            });
        }
    map: 龙台
    anyMatch: 龙台
    map: 百凯
    anyMatch: 百凯
    

    终端操作 anyMatch() 表示如果发现以 '百' 开头的元素,停止执行直接返回,所以 '英雄' 元素就没有被循环 ?? 执行

    如果仅仅是上面的方式可能无法直观的看出性能差距在哪,接下来以 mapfilter 的方式来进行操作

    Stream.of("龙台", "百凯", "驷马", "狂妄", "偏执", "英雄")
            .map(each -> {
                System.out.println("map: " + each);
                return each;
            })
            .filter(each -> {
                System.out.println("filter: " + each);
                return each.startsWith("英");
            })
            .forEach(each -> System.out.println("forEach: " + each));
    map: 龙台
    filter: 龙台
    map: 百凯
    filter: 百凯
    map: 驷马
    filter: 驷马
    map: 狂妄
    filter: 狂妄
    map: 偏执
    filter: 偏执
    map: 英雄
    filter: 英雄
    forEach: 英雄
    

    通过打印得知,执行顺序分别是一次 mapfilter ,如果将 filtermap 的执行顺序转换一下的话

    Stream.of("龙台", "百凯", "驷马", "狂妄", "偏执", "英雄")
            .filter(each -> {
                System.out.println("filter: " + each);
                return each.startsWith("英");
            })
            .map(each -> {
                System.out.println("map: " + each);
                return each;
            })
            .forEach(each -> System.out.println("forEach: " + each));
    filter: 龙台
    filter: 百凯
    filter: 驷马
    filter: 狂妄
    filter: 偏执
    filter: 英雄
    map: 英雄
    forEach: 英雄
    

    根据 Stream 的执行方式来正确的定义函数API的顺序,如果集合包含大量元素的情况下,这种性能提升还是很可观的
    值得一提的是 Streamsorted 函数是水平执行的,会首先将集合中的内容全部在 sorted 方法中执行后才会继续向下执行


    3 数据处理/转换

    一个流在被创建后,可以使用多个数据处理、转换操作,全部操作之后返回给终端操作的是一个新的流


    3.1 流的映射 map/flatMap

    在日常工作中通常会遇到这个场景,需要将一个集合对象中的某个属性提取出来重新组合成为一个新的集合,Stream 中的 mapflatMap 可以很好的完成这个需求,关键是:代码还很简洁 ??

    <R> Stream<R> map(Function<? super T, ? extends R> mapper);
    <R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
    

    map() 将学生集合中的学生名称抽取映射成为一个新的只包含名称的集合

    @Data
    public class Student {
    
        private String num, name, area;
    
        public Student(String num, String name, String area) {
            this.area = area; this.name = name; this.num = num;
        }
    
        public static void main