java8 -函数式编程之Stream

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

什么是Stream

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

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

    

Stream的特色

无存储性:数组

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

函数式编程:多线程

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

惰性求值:app

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

无限数据处理:dom

    集合的大小是有限的,可是流能够对无限的数据执行操做。好比可使用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表达式

相关文章
相关标签/搜索