java8 -函数式编程之Stream

孤街醉人 提交于 2019-11-27 04:37:54

        在 java8 -函数式编程之Lambda表达式、 java8 -函数式编程之四个基本接口 、java8 -函数式编程之Optional 三篇文章中,我们已经对函数式编程有了充分的了解,接下来,我们将会运用之前学到的知识学习项目中常用到的 java8 Stream 流式操作。

什么是Stream

        Stream API 借助于 Lambda 表达式,极大的提高编程效率和程序可读性。同时它提供串行和并行两种模式进行汇聚操作,并发模式能够充分利用多核处理器的优势,使用 fork/join 并行方式来拆分任务和加速处理过程。通常编写并行代码很难而且容易出错, 但使用 Stream API 无需编写一行多线程的代码,就可以很方便地写出高性能的并发程序。

        流主要有三部分构成:获取一个数据源(source)→ 数据转换 → 执行操作获取想要的结果。每次转换原有 Stream 对象不改变,返回一个新的 Stream 对象(可以有多次转换),这就允许对其操作可以像链条一样排列,变成一个管道。

    

Stream的特点

无存储性:

    流不是存储元素的数据结构;相反,它需要从数据结构,数组,生成器函数或IO管道中获取数据并通过流水线地(计算)操作对这些数据进行转换。

函数式编程:

    Stream上操作会产生一个新结果,而不会去修改原始数据。比如filter过滤操作它只会根据原始集合中将未被过滤掉的元素生成一个新的Stream,而不是真的去删除集合中的元素。

惰性求值:

    很多Stream操作(如filter,map,distinct等)都是惰性实现,这样做为了优化程序的计算。比如说,要从一串数字中找到第一个能被10整除的数,程序并不需要对这一串数字中的每个数字进行测试。流操作分为两种:中间操作(返回值仍为Stream,仍可执行操作),终断操作(结束Stream操作)。中间操作都是惰性操作。

无限数据处理:

    集合的大小是有限的,但是流可以对无限的数据执行操作。比如可以使用limit或findFirst这样的操作让Stream操作在有限的时间内结束。

一次性消费:

    流只能使用(“消费”)一次,一旦调用终断操作,流就不能再次使用,必须重新创建一个流。就像迭代器一样,遍历一遍后,想要再次遍历需要重新创建一个迭代器。

Stream的源的构建

有多种方式可以构建流:

    (1)静态工厂

    • Stream.of()
    • IntStream.of()
    • LongStream.of()
    • DoubleStream.of()

    (2)Collection 和数组

    • Collection.stream()
    • Collection.parallelStream()
    • Arrays.stream(T array) or Stream.of()

    (3)字符流

    • BufferdReader.lines()

    (4)文件路径

    • Files.walk()
    • Files.lines()
    • Files.find()

    (5)其它

    • Random.ints()
    • BitSet.stream()
    • Pattern.splitAsStream(java.lang.CharSequence)
    • JarFile.stream()

    生成流的时候,除了可以生成串行流,也可以生成并行流,即并行处理流的操作。

            final List<String> strings = Arrays.asList("ab", "a", "abc", "b", "bc");
    
            //串行流
            long count1 = strings.stream()
                    .filter(s -> {
                        System.out.println("thread:" + Thread.currentThread().getId());
                        return s.startsWith("a");
                    })
                    .count();
            System.out.println(count1);
    
            //并行流
            long count2 = strings.parallelStream()
                    .filter(s -> {
                        System.out.println("thread:" + Thread.currentThread().getId());
                        return s.startsWith("a");
                    })
                    .count();
            System.out.println(count2);

    Stream操作的分类

      Stream操作分类  
    中间操作(Intermediate operations) 无状态(Stateless)

    unordered(), filter(), map(), mapToInt(), mapToLong(), mapToDouble(), flatMap(), flatMapToInt(), flatMapToLong(), flatMapToDouble(), peek();

      有状态(Stateful) distinct();  sorted();  limit(), skip()
    终断操作(Terminal operations) 非短路操作 forEach(), forEachOrdered(); reduce(), collect(), max(), min(), count(); toArray()
      短路操作(short-circuiting) anyMatch(), allMatch(), noneMatch(); findFirst(), findAny()

    中间操作:

        返回一个新的Stream。中间操作都是惰性的,它们不会对数据源执行任何操作,仅仅是创建一个新的Stream。在终断操作执行之前,数据源的遍历不会开始。

    终断操作:

        遍历流并生成结果或者副作用。执行完终断操作后,Stream就会被“消费”掉,如果想再次遍历数据源,则必须重新创建新的Stream。大多数情况下,终断操作的遍历都是即时的——在返回之前完成数据源的遍历和处理,只有iterator()和spliterator()不是,这两个方法用于提供额外的遍历功能——让开发者自己控制数据源的遍历以实现现有Stream操作中无法满足的操作(实际上现有的Stream操作基本能满足需求,所以这两个方法目前用的不多)。

     

    Stream的操作

    中间操作

    • map

        使用传入的Function对象对Stream中的所有元素进行处理,返回的Stream对象中的元素为原元素处理后的结果。

            //map,平方数
            List<Integer> nums = Arrays.asList(1, 2, 3, 4);
            Stream<Integer> integerStream = nums.stream().map(n -> n * n);
            Collection<Integer> squareNums = integerStream.collect(Collectors.toList());
            squareNums.forEach(integer -> System.out.println(integer));

     

    • flatMap

        map 生成的是个 1:1 映射,每个输入元素,都按照规则转换成为另外一个元素。flatMap,则是一对多映射关系的。

            Stream<List<Integer>> inputStream = Stream.of(
                    Arrays.asList(1),
                    Arrays.asList(2, 3),
                    Arrays.asList(4, 5, 6)
            );
            Stream<Integer> integerStream1 = inputStream.flatMap(list -> list.stream());
            List<Integer> squareNums2 = integerStream1.map(n -> n * n).collect(Collectors.toList());
            squareNums2.forEach(integer -> System.out.println(integer));

     

    • filter

        filter 对原始 Stream 进行某项测试,通过测试的元素被留下来生成一个新 Stream

            final List<String> strings = Arrays.asList("ab", "a", "abc", "b", "bc");
            strings.stream()
                    .filter(s -> s.startsWith("a"))
                    .forEach(System.out::println);

     

    • peek

            peek,遍历Stream中的元素,和forEach类似,区别是peek不会“消费”掉Stream,而forEach会消费掉Stream;peek是中间操作所以也是惰性的,只有在Stream“消费”的时候生效。

            //peek
            Stream.of("one", "two", "three", "four")
                    .peek(e -> System.out.println("原来的值: " + e))
                    .map(String::toUpperCase)
                    .peek(e -> System.out.println("转换后的值: " + e))
                    .collect(Collectors.toList());

     

    • limit 和 skip

        limit取头部的数据(或者说截取前面的元素),skip取尾部的数据(跳过前面的元素)

            //limit, 返回 Stream 的前面 n 个元素
            final List<String> strings = Arrays.asList("ab", "a", "abc", "b", "bc");
            strings.stream()
                    .limit(3)
                    .forEach(System.out::println);
            System.out.println("==============");
    
            //skip 则是扔掉前 n 个元素(
            strings.stream()
                    .skip(3)
                    .forEach(System.out::println);
            System.out.println("==============");

     

    • distinct

        去除重复的元素

            Stream<String> distinctString = Stream.of("a","b","b","c")
                    .distinct();//去重
            distinctString.forEach(System.out::println);

     

    • sorted

        对Stream中的元素进行排序。有两个重载方法,其中 Stream<T> sorted() 需要元素实现了Comparable接口。

            Arrays.asList("ab", "a", "abc", "b", "bc").stream()
                    .sorted()
                    .forEach(System.out::println);
    
            Arrays.asList("ab", "a", "abc", "b", "bc").stream()
                    .sorted((o1, o2) -> {
                        return o1.compareTo(o2);
                    })
                    .forEach(System.out::println);
    

     

    终端操作

    短路操作

        短路操作其实就和我们日常编程用到的&&||运算符处理过程类似,遇到一个满足条件的就立即停止判断。

    • anyMatch

        只要其中有一个元素满足传入的Predicate时返回True,否则返回False。前面的中间操作只要anyMatch中的条件成立后,就不再执行。与逻辑运算符 || 类似。

            //anyMatch
            boolean anyMatchReturn = Arrays.asList("ab", "a", "abc", "b", "bc").stream()
                    .peek(s -> System.out.println(s))
                    .anyMatch(s -> s.startsWith("b"));
            System.out.println(anyMatchReturn);

    它的执行结果

    ab
    a
    abc
    b
    true

     

    • allMatch

        所有元素均满足传入的Predicate时返回True,否则False。只要allMatch条件有一个为false,中间操作将终止执行。与逻辑运算符&&类似

            boolean allMatchReturn = Arrays.asList("ab", "a", "abc", "b", "bc").stream()
                    .peek(s -> System.out.println(s))
                    .allMatch(s -> s.startsWith("b"));
            System.out.println(allMatchReturn);

    运行结果:

    ab
    false

     

    • noneMatch

        所有元素均不满足传入的Predicate时返回True,否则False。只要allMatch条件有一个为true,中间操作将终止执行。

            boolean noneMatchReturn = Arrays.asList("ab", "a", "abc", "b", "bc").stream()
                    .peek(s -> System.out.println(s))
                    .noneMatch(s -> s.startsWith("b"));
            System.out.println(noneMatchReturn);

    结果:

    ab
    a
    abc
    b
    false

     

    非短路操作

    • forEach

        对所有元素进行迭代处理,无返回值

            Arrays.asList("ab", "a", "abc", "b", "bc").forEach(s -> {
                System.out.println(s);
            });

     

    • reduce

        计算机术语:规约,通过累加器accumulator,对前面的序列进行累计操作,并最终返回一个值。累加器accumulator有两个参数,第一个是前一次累加的结果,第二个是前面集合的下一个元素。通过reduce,可以实现 average, sum, min, max, count。reduce有三个重载方法:

        (1)T reduce(T identity, BinaryOperator<T> accumulator) :这里的identity是初始值。下面将会把几个字符组装成一个字符串

            String concat = Stream.of("A", "B", "C", "D").reduce("H",
                    (x, y) -> {
                        System.out.println("x=" + x + ", y=" + y);
                        return x.concat(y);
                    });
            System.out.println(concat);

        输出结果:

    x=H, y=A
    x=HA, y=B
    x=HAB, y=C
    x=HABC, y=D
    HABCD

            

    (2)Optional<T> reduce(BinaryOperator<T> accumulator):由于没有初始值,这里输出Optional类型,避免空指针

            Optional<String> concat2Optional = Stream.of("A", "B", "C", "D").reduce(
                    (x, y) -> {
                        System.out.println("x=" + x + ", y=" + y);
                        return x.concat(y);
                    });
            System.out.println(concat2Optional.orElse("default"));

    输出结果:

    x=A, y=B
    x=AB, y=C
    x=ABC, y=D
    ABCD

     

        (3)<U> U reduce(U identity,  BiFunction<U, ? super T, U> accumulator,  BinaryOperator<U> combiner)

        这个方法非常复杂,如果感兴趣可以参考其它文章: Java8新特性学习-Stream的Reduce及Collect方法详解

     

    • collect

        collect方法可以通过收集器collector将流转化为其他形式,比如字符串、list、set、map。collect有两个重载方法,其中一个是最常用的:

        <R, A> R collect(Collector<? super T, A, R> collector)。官方为了我们转换方便,已经在Collectors类中封装了各种各样的collector。下面看一些常用的收集器。

     

    拼接字符串

            //collect,拼接字符串
            String collect1 = Stream.of("A", "B", "C", "D")
                    .collect(Collectors.joining());
            System.out.println(collect1);

    转成List

         //collect,转成arrayList
            List<String> collect2 = Stream.of("A", "B", "C", "D")
                    .collect(Collectors.toList());

    转成set

            Set<String> collect3 = Stream.of("A", "B", "C", "D")
                    .collect(Collectors.toSet());

     

    转成map

    Collectors的toMap方法签名如下所示,前一个mapper转换成map中的key,后一个mapper转换成map中的value

    Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,
                                    Function<? super T, ? extends U> valueMapper)

            Map<String, String> collect3 = Stream.of("A", "B", "C", "D")
                    .collect(Collectors.toMap(
                            s -> s,
                            s -> s
                    ));

     

    相关资料

    Java 8 函数式编程系列

        java8 -函数式编程之Lambda表达式

        java8 -函数式编程之四个基本接口

        java8 -函数式编程之Optional

        java8 -函数式编程之Stream

     

    参考资料:

    Java函数式编程与Lambda表达式

    易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
    该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!