在JAVA 8的Stream方法中,分为两大类,一类是惰性求值,另一类是立刻求值,只要Stream调用了立刻求值,Stream就会自动关闭,如果再次调用,将会提示如下错误:
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.reduce(ReferencePipeline.java:479)
at com.mirana.stream.FlatMapReduceTest.testFlatMap(FlatMapReduceTest.java:49)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
这样的情况,就给我们摆出了一个难题,如果我们既要进行元素个数统计,又要对元素进行求和,两个立刻求值的函数,必然只能执行一个,怎么办呢?
查遍了Stream的官方接口,发现并没有解决此问题的方法,但是我们可以采用封装的方式解决此问题,即通过调用函数来获取新的Stream对象,从而分离Stream实例,如下:
private List<String> joins = Lists.newArrayList("1, 2, 3", "2, 3, 4", "3, 4, 5");
// 通过函数每次返回新的Stream对象
public Stream<Integer> getStream() {
// 将字符转换为数字Stream
return joins.stream().flatMap(str -> {
String[] nums = str.split(",");
// 将字符转换为数字
return Arrays.asList(nums).stream().map(num -> {
return Integer.parseInt(num.trim());
});
});
}
从上面的例子可以看出:
1. Stream并没有依赖于可迭代对象的并发锁,只要调用stream()方法就会新的Stream实例,如上例中的joins对象可多次创建Stream实例,实例之间并没有相互影响;
2. flatMap方法与map方法的区别在于函数接口的返回值(注意,是函数接口,而不是方法自身,即Function.apply方法),flatMap必须返回Stream,而Map没有此限制;
如果要在函数内定义内调用,上述的方法还可以采用lambda的样式,如下:
@Test
public void testReStream() {
// 直接在函数内定义函数
Supplier<Stream<Integer>> supplier = () -> joins.stream().flatMap(str -> {
String[] nums = str.split(",");
return Arrays.asList(nums).stream().map(num -> {
return Integer.parseInt(num.trim());
});
});
// 计数
assertThat(supplier.get().count(), IsEqual.equalTo(9L));
// 求和
int sum = supplier.get().reduce((a, b) -> a + b).get();
assertThat(sum, IsEqual.equalTo(27));
}
结论
JAVA 8的Stream类并没有直接提供复用的方法,只能采用封装的方式进行复用,所以也只是代码的复用,性能并没有提升。