Java8のStream学习

转载自:http://www.ibm.com/developerworks/cn/java/j-lo-java8streamapi/

       JDK中已经增加了一个新包java.util.stream,能够使用Java8集合类库执行类似filter/map/reduce的操作。这个流式API使我们能在数据流之上编写串行或者并行的操作。
       Stream操作分为中间操作或者最终操作两种,最终操作返回一特定类型的计算结果,而中间操作返回Stream本身,这样就可以将多个操作依次串起来。 Stream 的创建需要指定一个数据源,比如 java.util.Collection的子类,List或者Set,Map不支持。Stream的操作可以串行执行或者并行执行。

       Stream 不是集合元素,它不是数据结构并不保存数据,它是有关算法和计算的,它更像一个高级版本的 Iterator。
       当使用一个流的时候,通常包括三个基本步骤:获取一个数据源(source)→ 数据转换→执行操作获取想要的结果,每次转换原有 Stream 对象不改变,返回一个新的 Stream 对象(可以有多次转换),这就允许对其操作可以像链条一样排列,变成一个管道。

流的操作类型

       流的操作类型分为两种:
       Intermediate(中间操作):一个流可以后面跟随零个或多个 intermediate 操作。其目的主要是打开流,做出某种程度的数据映射/过滤,然后返回一个新的流,交给下一个操作使用。这类操作都是惰性化的(lazy),就是说,仅仅调用到这类方法,并没有真正开始流的遍历。

       Terminal(末端操作):一个流有且仅有一个terminal 操作,当这个操作执行后,流就被使用“光”了,无法再被操作。所以这必定是流的最后一个操作。Terminal 操作的执行,才会真正开始流的遍历,并且会生成一个结果,或者一个或者导致一个副作用(side effect)。


       在对于一个 Stream 进行多次转换操作(Intermediate 操作),每次都对 Stream 的每个元素进行转换,而且是执行多次,这样时间复杂度就是 N(转换次数)个for循环里把所有操作都做掉的总和吗?其实不是这样的,转换操作都是 lazy 的,多个转换操作只会在 Terminal 操作的时候融合起来,一次循环完成。可以这样简单的理解,Stream 里有个操作函数的集合,每次转换操作就是把转换函数放入这个集合中,在Terminal操作的时候循环 Stream 对应的集合,然后对每个元素执行所有的函数。

       还有一种操作被称为 short-circuiting(短循环)。指:
           对于一个 intermediate 操作,如果它接受的是一个无限大(infinite/unbounded)的Stream,但返回一个有限的新 Stream。
           对于一个 terminal 操作,如果它接受的是一个无限大的 Stream,但能在有限的时间计算出结果。
       当操作一个无限大的 Stream,而又希望在有限时间内完成操作,则在管道内拥有一个 short-circuiting 操作是必要非充分条件。

       接下来,当把一个数据结构包装成 Stream 后,就要开始对里面的元素进行各类操作了。常见的操作可以归类如下:
Intermediate(中间操作):
       map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 limit、 skip、 parallel、 sequential、 unordered
Terminal(末端操作):
       forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、 anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 iterator
Short-circuiting(短循环):
       anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 limit

流的使用详解

流的构造与转换

[java]  view plain  copy
  1. // 1. Individual values  
  2. Stream stream = Stream.of("a""b""c");  
  3. // 2. Arrays  
  4. String [] strArray = new String[] {"a""b""c"};  
  5. stream = Stream.of(strArray);  
  6. stream = Arrays.stream(strArray);  
  7. // 3. Collections  
  8. List<String> list = Arrays.asList(strArray);  
  9. // 串行  
  10. stream = list.stream();  
  11. // 并行  
  12. Stream<String> parallelStream = list.parallelStream();  

流的操作

map/flatMap
它的作用就是把 input Stream 的每一个元素,映射成output Stream 的另外一个元素。
[java]  view plain  copy
  1. Stream.of("one""two""three""four").map(String::toUpperCase).forEach(s -> System.out.println(s));  
  2. Stream.of("one""two""three""four").map(s->s.substring(02)).forEach(s -> System.out.println(s));  
从上面例子可以看出,map 生成的是个 1:1 映射,每个输入元素,都按照规则转换成为另外一个元素。还有一些场景,是一对多映射关系的,这时需要 flatMap。

filter
filter 对原始 Stream 进行某项测试,通过测试的元素被留下来生成一个新 Stream。即排除所有不满足条件的元素。

forEach
       forEach 方法接收一个 Lambda 表达式,然后在 Stream 的每一个元素上执行该表达式。当需要为多核系统优化时,可以 parallelStream().forEach(),只是此时原有元素的次序没法保证,并行的情况下将改变串行时操作的行为,此时 forEach 本身的实现不需要调整。但一般认为,forEach 和常规 for循环的差异不涉及到性能,它们仅仅是函数式风格与传统 Java 风格的差别。
       另外一点需要注意,forEach 是terminal(末端)操作,因此它执行后,Stream 的元素就被“消费”掉了,无法对一个Stream 进行两次 terminal运算。下面的代码是错误的,会报java.lang.IllegalStateException: stream has already been operated upon or closed异常:
[java]  view plain  copy
  1. stream.forEach(System.out::print);  
  2. stream.forEach(System.out::print);  

peek 
对每个元素执行操作并返回一个新的Stream。

reduce
       这个方法的主要作用是把 Stream 元素组合起来。它提供一个起始值(种子),然后依照运算规则(BinaryOperator),和前面 Stream 的第一个、第二个、第 n 个元素组合。从这个意义上说,字符串拼接、数值的 sum、min、max、average 都是特殊的 reduce。
[java]  view plain  copy
  1. // 字符串连接,concat = "ABCD"  
  2. String concat = Stream.of("A""B""C""D").reduce("", String::concat);  
  3.   
  4. // 求和,sumValue = 10, 有起始值  
  5. int sumValue = Stream.of(1234).reduce(0, Integer::sum);  
  6.   
  7. // 求和,sumValue = 10, 无起始值,返回的是 Optional。  
  8. int sum = Stream.of(1234).reduce(Integer::sum).get();  

limit/skip
limit返回 Stream 的前面 n 个元素;skip 则是扔掉前 n 个元素(它是由一个叫 subStream 的方法改名而来)。

sorted
       对Stream 的排序通过 sorted 进行,它比数组的排序更强之处在于你可以首先对 Stream 进行各类 map、filter、limit、skip 甚至 distinct 来减少元素数量后,再排序,这能帮助程序明显缩短执行时间。我们对清单 14 进行优化:

Match
Stream 有三个 match 方法,从语义上说: 
       allMatch:Stream 中全部元素符合传入的 predicate,返回 true
       anyMatch:Stream 中只要有一个元素符合传入的 predicate,返回 true
       noneMatch:Stream 中没有一个元素符合传入的 predicate,返回 true
它们都不是要遍历全部元素才能返回结果。例如 allMatch 只要一个元素不满足条件,就 skip 剩下的所有元素,返回 false。

猜你喜欢

转载自blog.csdn.net/salonzhou/article/details/50777498