助你写出更加有效、简洁、紧凑的代码-Stream
2018-04-01 15:20
531 查看
JAVA8中引入的Stream与 I/O中的InputStream和OutputStream是两个不同的概念。这里的Stream其实是函数式编程里Monad的概念。(在函数式编程中,monad是一个表示计算(步骤序列)的结构。一个带有monad结构的类型或该类型的嵌套函数定义了其链式操作的意义。)
Stream中的操作可以分为两大类:
中间操作与结束操作:
1. 中间操作:中间操作返回Stream,这样我们就可以在不使用分号的情况下串联多个中间操作
中间操作又可以分为无状态(Stateless)操作与有状态(Stateful)操作,前者是指元素的处理不受之前元素的影响;后者是指该操作只有拿到所有元素之后才能继续下去。
2. 结束操作:终端操作返回void或者一个非Stream结果值
结束操作又可以分为短路与非短路操作,这个应该很好理解,前者是指遇到某些符合条件的元素就可以得到最终结果;而后者是指必须处理所有元素才能得到最终结果。
不同类型的Stream
可以从各种数据源创建Stream,特别是collections,List 和 Set, 支持新方法 stream() 和parallelStream(),以创建顺序或并行Stream。如:
使用 Stream.of() 从一堆对象引用中创建一个Stream,如:
除了常规的对象Stream,Java 8有特殊类型的Stream,用于处理基本数据类型int,long和double。分别是IntStream、LongStream和DoubleStream。
IntStreams可以使用IntStream.range()来代替常规的for循环,如:
将普通Stream转为原生类型的Steam:mapToInt()、mapToLong()和mapToDouble
处理顺序
中间操作的一个重要特征是惰性。以下例子中,终端操作是缺失的,在执行此代码片段时,不会向控制台输出任何内容。这是因为中间操作只在出现终端操作时执行:
通过终端操作forEach来扩展上面的例子:
控制台输出:
输出顺序:每个元素都沿着链垂直移动。第一个字符串“d2”先filter然后foreach,然后第二个字符串“a2”才被处理。
这种方式可以减少在每个元素上执行的实际操作数,如下例所示:
控制台输出:
当predicate应用于给定的输入元素时,anyMatch将立即返回true。这对于第二个被传递的“a2”来说是正确的。由于stream链的垂直执行,在这种情况下,filter只会执行两次。因此,filter将尽可能少地被调用。
处理顺序很重要
下一个示例包括两个中间操作 map 和 filter 以及终端操作forEach。我们再一次查看这些操作是如何执行的:
控制台输出:
如输出结果所示,底层集合中的每个字符串都被调用了5次map和filter,而forEach只调用一次。
改变操作的顺序,将filter移到链的开头,可以大大减少实际执行次数:
控制台输出:
如输出结果所示,map只被调用一次,因此操作管道在大量元素输入时执行得更快。
Stream复用
Stream在你调用任何终端操作后,就会关闭:
在同一条Stream上的调用anyMatch之后调用noneMatch导致以下异常:
可通过为要执行的每一个终端操作创建一个新的Stream链的方式解决上述问题。
Stream中的操作可以分为两大类:
中间操作与结束操作:
1. 中间操作:中间操作返回Stream,这样我们就可以在不使用分号的情况下串联多个中间操作
中间操作又可以分为无状态(Stateless)操作与有状态(Stateful)操作,前者是指元素的处理不受之前元素的影响;后者是指该操作只有拿到所有元素之后才能继续下去。
2. 结束操作:终端操作返回void或者一个非Stream结果值
结束操作又可以分为短路与非短路操作,这个应该很好理解,前者是指遇到某些符合条件的元素就可以得到最终结果;而后者是指必须处理所有元素才能得到最终结果。
不同类型的Stream
可以从各种数据源创建Stream,特别是collections,List 和 Set, 支持新方法 stream() 和parallelStream(),以创建顺序或并行Stream。如:
Arrays.asList("a1", "a2", "a3") .stream() .findFirst() .ifPresent(System.out::println);
使用 Stream.of() 从一堆对象引用中创建一个Stream,如:
Stream.of("a1", "a2", "a3") .findFirst() .ifPresent(System.out::println);
除了常规的对象Stream,Java 8有特殊类型的Stream,用于处理基本数据类型int,long和double。分别是IntStream、LongStream和DoubleStream。
IntStreams可以使用IntStream.range()来代替常规的for循环,如:
IntStream.range(1, 4) .forEach(System.out::println);
将普通Stream转为原生类型的Steam:mapToInt()、mapToLong()和mapToDouble
处理顺序
中间操作的一个重要特征是惰性。以下例子中,终端操作是缺失的,在执行此代码片段时,不会向控制台输出任何内容。这是因为中间操作只在出现终端操作时执行:
Stream.of("d2", "a2", "b1", "b3", "c") .filter(s -> { System.out.println("filter: " + s); return true; });
通过终端操作forEach来扩展上面的例子:
Stream.of("d2", "a2", "b1", "b3", "c") .filter(s -> { System.out.println("filter: " + s); return true; }) .forEach(s -> System.out.println("forEach: " + s));
控制台输出:
filter: d2 forEach: d2 filter: a2 forEach: a2 filter: b1 forEach: b1 filter: b3 forEach: b3 filter: c forEach: c
输出顺序:每个元素都沿着链垂直移动。第一个字符串“d2”先filter然后foreach,然后第二个字符串“a2”才被处理。
这种方式可以减少在每个元素上执行的实际操作数,如下例所示:
Stream.of("d2", "a2", "b1", "b3", "c") .filter(s -> { System.out.println("filter: " + s); return true; }) .anyMatch(ss->{ System.out.println("anyMatch: "+ss); return ss.equals("a2"); });
控制台输出:
filter: d2 anyMatch: d2 filter: a2 anyMatch: a2
当predicate应用于给定的输入元素时,anyMatch将立即返回true。这对于第二个被传递的“a2”来说是正确的。由于stream链的垂直执行,在这种情况下,filter只会执行两次。因此,filter将尽可能少地被调用。
处理顺序很重要
下一个示例包括两个中间操作 map 和 filter 以及终端操作forEach。我们再一次查看这些操作是如何执行的:
Stream.of("d2", "a2", "b1", "b3", "c") .map(s -> { System.out.println("map: " + s); return s.toUpperCase(); }) .filter(s -> { System.out.println("filter: " + s); return s.startsWith("A"); }) .forEach(s -> System.out.println("forEach: " + s));
控制台输出:
map: d2 filter: D2 map: a2 filter: A2 forEach: A2 map: b1 filter: B1 map: b3 filter: B3 map: c filter: C
如输出结果所示,底层集合中的每个字符串都被调用了5次map和filter,而forEach只调用一次。
改变操作的顺序,将filter移到链的开头,可以大大减少实际执行次数:
Stream.of("d2", "a2", "b1", "b3", "c") .filter(s -> { System.out.println("filter: " + s); return s.startsWith("a"); }) .map(s -> { System.out.println("map: " + s); return s.toUpperCase(); }) .forEach(s -> System.out.println("forEach: " + s));
控制台输出:
filter: d2 filter: a2 map: a2 forEach: A2 filter: b1 filter: b3 filter: c
如输出结果所示,map只被调用一次,因此操作管道在大量元素输入时执行得更快。
Stream复用
Stream在你调用任何终端操作后,就会关闭:
Stream<String> stringStream = Stream.of("d2", "a2", "b1", "b3", "c") .filter(s -> { System.out.println("filter: " + s); return true; }); stringStream.anyMatch(ss->{ System.out.println("anyMatch: "+ss); return ss.equals("a2"); }); stringStream.forEach(ss->{ System.out.println("forEach: "+ss); });
在同一条Stream上的调用anyMatch之后调用noneMatch导致以下异常:
Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:229) at java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:418) at com.java8.stream.StreamTest.t8(StreamTest.java:113) at com.java8.stream.StreamTest.main(StreamTest.java:26)
可通过为要执行的每一个终端操作创建一个新的Stream链的方式解决上述问题。
Supplier<Stream<String>> streamSupplier = ()->Stream.of("d2", "a2", "b1", "b3", "c") .filter(s -> { System.out.println("filter: " + s); return true; }); streamSupplier.get().anyMatch(ss->{ System.out.println("anyMatch: "+ss); return ss.equals("a2"); }); streamSupplier.get().forEach(ss->{ System.out.println("forEach: "+ss); });
相关文章推荐
- 学习 | Python之高级特性:如何写出少而有效的代码
- Java 8 stream: 让你的代码更简洁
- 几点建议帮你写出简洁的JS代码
- 在C#中用最简洁有效的代码执行存储过程并返回数据
- 使用小书匠代码语法高亮功能,写出更加容易阅读的代码
- AndroidAnnotations一个可以让你的android代码更加简洁的框架
- 几点建议帮你写出简洁的JS代码-转自别人的博客
- 五个方面促进你写出更加专业的CSS代码
- 在C#中用最简洁有效的代码执行存储过程并返回数据
- 如何有效的提高代码重复性,代码简洁--经典之谈
- 分享10个帮助你编写更加简洁javascript代码的小技巧
- 在C#中用最简洁有效的代码执行存储过程并返回数据
- 记录下一个C++初始化的方式(很少有人这么用,但是却是一个使代码更加简洁的方式)
- 一个非常简洁有效的判断IP地址格式是否正确的函数,c++代码
- 教你打造一个EvenBus 通用的model让你的代码更加简洁
- 使用vc2010的c++0x特性,我们可以写出简洁有趣的代码
- 更加简洁的代码从MVC到MVP
- 从源代码剖析modelDriven拦截器和params拦截器和拦截器prepare 和paramsPrepareParamsStack拦截器栈(使您的Struts2代码更加简洁——怎样培养框架设计能力
- 在C#中用最简洁有效的代码执行存储过程并返回数据
- 用最简洁有效的代码执行存储过程 C#