Java8 Stream

        Java 8 API添加了一个新的抽象称为流Stream

        Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。

        这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。

        元素流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前面处理的结果。

什么是 Stream?

       Stream(流)是一个来自数据源的元素队列并支持聚合操作

  • 元素是特定类型的对象,形成一个队列。 Java中的Stream并不会存储元素,而是按需计算。
  • 数据源 流的来源。 可以是集合,数组,I/O channel, 产生器generator 等。
  • 聚合操作 类似SQL语句一样的操作, 比如filter, map, reduce, find, match, sorted等。

       和以前的Collection操作不同, Stream操作还有两个基础的特征:

  • Pipelining: 中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。 这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)。
  • 内部迭代: 以前对集合遍历都是通过Iterator或者For-Each的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。 Stream提供了内部迭代的方式, 通过访问者模式(Visitor)实现。
        java8的流式处理极大的简化了对于集合的操作,实际上不光是集合,包括数组、文件等,只要是可以转换成流,我们都可以借助流式处理,类似于我们写SQL语句一样对其进行操作。java8通过内部迭代来实现对流的处理,一个流式处理可以分为三个部分:转换成流、中间操作、终端操作。如下图:

一、生成流

从集合生成流:

       在 Java 8 中, 集合接口有两个方法来生成流:

  • stream() − 为集合创建串行流。

  • parallelStream() − 为集合创建并行流。

List < String > strings = Arrays . asList ( " abc " , " " , " bc " , " efg " , " abcd " , " " , " jkl " ) ;
List < String > filtered = strings . stream ( ) . filter ( string -> ! string . isEmpty ( ) ) . collect ( Collectors . toList ( ) ) ;

从文件生成流:


二、中间操作

forEach

Stream 提供了新的方法 'forEach' 来迭代流中的每个数据。以下代码片段使用 forEach 输出了10个随机数:

Random random = new Random ( ) ;
random . ints ( ) . limit ( 10 ) . forEach ( System . out :: println ) ;

映射:

       在SQL中,借助 SELECT关键字后面添加需要的字段名称,可以仅输出我们需要的字段数据,而流式处理的映射操作也是实现这一目的,在java8的流式处理中,主要包含两类映射操作:map和flatMap。

map

       map 方法用于映射每个元素到对应的结果,以下代码片段使用 map 输出了元素对应的平方数:

List < Integer > numbers = Arrays . asList ( 3 , 2 , 2 , 3 , 7 , 3 , 5 ) ; // 获取对应的平方数
List < Integer > squaresList = numbers . stream ( ) . map ( i -> i * i ) . distinct ( ) . collect ( Collectors . toList ( ) ) ;

       假设我们希望筛选出所有专业为计算机科学的学生姓名,那么我们可以在filter筛选的基础之上,通过map将学生实体映射成为学生姓名字符串,具体实现如下:
List names = students.stream()
                            .filter(student -> "计算机科学".equals(student.getMajor()))
                            .map(Student::getName).collect(Collectors.toList());

       除了上面这类基础的map,java8还提供了mapToDouble(ToDoubleFunctionmapToInt(ToIntFunctionmapToLong(ToLongFunction,这些映射分别返回对应类型的流,java8为这些流设定了一些特殊的操作,比如我们希望计算所有专业为计算机科学学生的年龄之和,那么我们可以实现如下:
int totalAge = students.stream()
                    .filter(student -> "计算机科学".equals(student.getMajor()))
                    .mapToInt(Student::getAge).sum();
       使用这些数值流的好处还在于可以避免jvm装箱操作所带来的性能消耗。

flatmap

       flatMap与map的区别在于 flatMap是将一个流中的每个值都转成一个个流,然后再将这些流扁平化成为一个流

       举例说明,假设我们有一个字符串数组String[] strs = {"java8", "is", "easy", "to", "use"};,我们希望输出构成这一数组的所有非重复字符,那么我们可能首先会想到如下实现:

List distinctStrs = Arrays.stream(strs)
                                .map(str -> str.split(""))  // 映射成为Stream                                .distinct()
                                .collect(Collectors.toList());

 
    

       在执行map操作以后,我们得到是一个包含多个字符串(构成一个字符串的字符数组)的流,此时执行distinct操作是基于在这些字符串数组之间的对比,所以达不到我们希望的目的,此时的输出为:

[j, a, v, a, 8][i, s][e, a, s, y][t, o][u, s, e]

 
    

       distinct只有对于一个包含多个字符的流进行操作才能达到我们的目的,即对Stream进行操作。此时flatMap就可以达到我们的目的:

List distinctStrs = Arrays.stream(strs)
                                .map(str -> str.split(""))  // 映射成为Stream                                .flatMap(Arrays::stream)  // 扁平化为Stream                                .distinct()
                                .collect(Collectors.toList());

        flatMap将由map映射得到的Stream,转换成由各个字符串数组映射成的流Stream,再将这些小的流扁平化成为一个由所有字符串构成的大流Steam,从而能够达到我们的目的。

  与map类似,flatMap也提供了针对特定类型的映射操作:flatMapToDouble(Function)flatMapToInt(Function)flatMapToLong(Function)



过滤:

过滤,顾名思义就是按照给定的要求对集合进行筛选满足条件的元素,java8提供的筛选操作包括:filter、distinct、limit、skip。

filter

filter 方法用于通过设置的条件过滤出元素。filter接受一个谓词Predicate,我们可以通过这个谓词定义筛选条件,在介绍lambda表达式时我们介绍过Predicate是一个函数式接口,其包含一个test(T t)方法,该方法返回boolean。以下代码片段使用 filter 方法过滤出空字符串:
List < String > strings = Arrays . asList ( " abc " , " " , " bc " , " efg " , " abcd " , " " , " jkl " ) ; // 获取空字符串的数量
int count = strings . stream ( ) . filter ( string -> string . isEmpty ( ) ) . count ( ) ;

distinct

distinct操作类似于我们在写SQL语句时,添加的DISTINCT关键字,用于去重处理,distinct基于Object.equals(Object)实现,假设我们希望筛选出所有不重复的偶数,那么可以添加distinct操作:

List evens = nums.stream()
                        .filter(num -> num % 2 == 0).distinct()
                        .collect(Collectors.toList());


limit

limit 方法用于获取指定数量的流。limit操作也类似于SQL语句中的LIMIT关键字,不过相对功能较弱,limit返回包含前n个元素的流,当集合大小小于n时,则返回实际长度,以下代码片段使用 limit 方法打印出 10 条数据:

Random random = new Random ( ) ;
random . ints ( ) . limit ( 10 ) . forEach(System.out::println);

说到limit,不得不提及一下另外一个流操作:

sorted

sorted 方法用于对流进行排序,sorted要求待比较的元素必须实现 Comparable接口,如果没有实现也不要紧,我们可以将比较器作为参数传递给 sorted(Comparator)。以下代码片段使用 sorted 方法对输出的 10 个随机数进行排序:
Random random = new Random ( );
random . ints ( ) . limit ( 10 ) . sorted ( ) . forEach ( System . out :: println ) ;

skip

skip操作与limit操作相反,如同其字面意思一样,是跳过前n个元素。通过skip,就会跳过前面两个元素,返回由后面所有元素构造的流,如果n大于满足条件的集合的长度,则会返回一个空的集合。以下代码片段使用 skip方法对输出的 10 个随机数取排序在2之后的数:

Random random = new Random ( ) ;
random . ints ( ) . limit ( 10 ) . skip (2 ) . forEach ( System . out :: println ) ;


三、终端操作

终端操作是流式处理的最后一步,我们可以在终端操作中实现对流查找、归约等操作。

1.查找

allMatch

  allMatch用于检测是否全部都满足指定的参数行为,如果全部满足则返回true

boolean isAdult = students.stream().allMatch(student -> student.getAge() >= 18);

anyMatch

  anyMatch则是检测是否存在一个或多个满足指定的参数行为,如果满足则返回true

boolean hasWhu = students.stream().anyMatch(student -> "武汉大学".equals(student.getSchool()));

noneMatch

        noneMatch用于检测是否不存在满足指定行为的元素,如果不存在则返回true
boolean noneCs = students.stream().noneMatch(student -> "计算机科学".equals(student.getMajor()));

findFirst

        findFirst用于返回满足条件的第一个元素

findFirst不携带参数,具体的查找条件可以通过filter设置,此外我们可以发现findFirst返回的是一个Optional类型,关于该类型的具体讲解可以参考上一篇:Java8新特性 - Optional类。http://www.zhenchao.org/2016/09/24/java8-optional/

findAny

findAny相对于findFirst的区别在于,findAny不一定返回第一个,而是返回任意一个
实际上对于顺序流式处理而言,findFirst和findAny返回的结果是一样的,至于为什么会这样设计,是因为在下一篇我们介绍的  并行流式处理,当我们启用并行流式处理的时候,查找第一个元素往往会有很多限制,如果不是特别需求,在并行流式处理中使用findAny的性能要比findFirst好。

2.归约

前面的例子中我们大部分都是通过 collect(Collectors.toList())对数据封装返回,如我的目标不是返回一个新的集合,而是希望对经过参数化操作后的集合进行进一步的运算,那么我们可用对集合实施归约操作。java8的流式处理提供了 reduce方法来达到这一目的。

并行(parallel)程序

parallelStream 是流并行处理程序的代替方法。以下实例我们使用 parallelStream 来输出空字符串的数量:

List < String > strings = Arrays . asList ( " abc " , " " , " bc " , " efg " , " abcd " , " " , " jkl " ) ; // 获取空字符串的数量
int count = strings . parallelStream ( ) . filter ( string -> string . isEmpty ( ) ) . count ( ) ;

我们可以很容易的在顺序运行和并行直接切换。


四、并行流式数据处理

        parallelStream 是流并行处理程序的代替方法。以下实例我们使用 parallelStream 来输出空字符串的数量:

List < String > strings = Arrays . asList ( " abc " , " " , " bc " , " efg " , " abcd " , " " , " jkl " ) ; // 获取空字符串的数量
int count = strings . parallelStream ( ) . filter ( string -> string . isEmpty ( ) ) . count ( ) ;
        我们可以很容易的在顺序运行和并行直接切换。

        流式处理中的很多都适合采用 分而治之 的思想,从而在处理集合较大时,极大的提高代码的性能,java8的设计者也看到了这一点,所以提供了 并行流式处理。上面的例子中我们都是调用stream()方法来启动流式处理,java8还提供了parallelStream()来启动并行流式处理,parallelStream()本质上基于java7的Fork-Join框架实现,其默认的线程数为宿主机的内核数。
  启动并行流式处理虽然简单,只需要将stream()替换成parallelStream()即可,但既然是并行,就会涉及到多线程安全问题,所以在启用之前要先确认并行是否值得(并行的效率不一定高于顺序执行),另外就是要保证线程安全。此两项无法保证,那么并行毫无意义,毕竟结果比速度更加重要。


Collectors

Collectors 类实现了很多归约操作,例如将流转换成集合和聚合元素。Collectors 可用于返回列表或字符串:

List < String > strings = Arrays . asList ( " abc " , " " , " bc " , " efg " , " abcd " , " " , " jkl " ) ;
List < String > filtered = strings . stream ( ) . filter ( string -> ! string . isEmpty ( ) ) . collect ( Collectors . toList ( ) ) ;
System . out . println ( " 筛选列表: " + filtered ) ;
String mergedString = strings . stream ( ) . filter ( string -> ! string . isEmpty ( ) ) . collect ( Collectors . joining ( " , " ) ) ;
System . out . println ( " 合并字符串: " + mergedString ) ;

统计

另外,一些产生统计结果的收集器也非常有用。它们主要用于int、double、long等基本类型上,它们可以用来产生类似如下的统计结果。

List < Integer > numbers = Arrays . asList ( 3 , 2 , 2 , 3 , 7 , 3 , 5 ) ;
IntSummaryStatistics stats = integers . stream ( ) . mapToInt ( ( x ) -> x ) . summaryStatistics ( ) ;
System . out . println ( " 列表中最大的数 : " + stats . getMax ( ) ) ;
System . out . println ( " 列表中最小的数 : " + stats . getMin ( ) ) ;
System . out . println ( " 所有数之和 : " + stats . getSum ( ) ) ;
System . out . println ( " 平均数 : " + stats . getAverage ( ) ) ;




https://my.oschina.net/wangzhenchao/blog/754726

http://www.runoob.com/java/java8-streams.html
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值