关于Stream流
Stream流
是一系列元素支持串行和并行聚合操作
- Collection提供了新的
stream()
方法 - 流不存储值,而是通过管道的方式获取值
- 流的本质是函数式的—即对流的操作会生成一个结果,不过并不会修改底层的数据源,集合可以作为流的底层数据源
- 延迟查找—很多流操作(过滤,映射,排序等)都可以延迟实现
与传统迭代器的不同之处
- 和迭代器不同的是,Stream可以并行化操作,而迭代器只能命令式地,串行化操作
- 当使用串行方式去遍历时,每个元素读完后再读下一个元素
- 使用并行去遍历时,数据会被分成多个段,其中每一个都在
不同的线程
中处理,然后将结果一起输出 - Stream的并行操作依赖于
Java7
引入的Fork/Join
框架
1 Stream流的构成
Stream流由三部分构成
- 源
- 零个或多个中间操作
- 终止操作
2 Stream流操作的分类
- 惰性求值—零个或多个操作
- 及早求值—终止操作
3 得到Stream流的三种方式
-
通过流的静态方法
-
通过数组的方式
-
通过集合的方式
-
示例
public class StreamTest {
public static void main(String[] args) {
//1.通过流的静态方法
Stream<String> stream1 = Stream.of("hello", "world", "hello world");
Stream<String> stream2 = Stream.generate(UUID.randomUUID()::toString);
Stream<Integer> stream3 = Stream.iterate(1, item -> item + 2).limit(10);
//2.通过数组的方式
String[] arr = {"hello", "world", "hello world"};
Stream<String> stream2 = Stream.of(arr);
//3.通过集合的方式
List<String> list = Arrays.asList("hello", "world", "hello world");
Stream<String> stream3 = list.stream();
}
}
4 通过使用流来简化代码
- 举例
public class SteamTest {
public static void main(String[] args) {
//迭代输出1,2,3
IntStream.of(1, 2, 3).forEach(System.out :: println);
System.out.println("+++++");
//输出3,8(不包含8)之间的数
IntStream.range(3, 8).forEach(System.out :: println);
System.out.println("+++++");
//输出3,8(包含8)之间的数
IntStream.rangeClosed(3, 8).forEach(System.out :: println);
System.out.println("+++++");
//将整型list中元素两倍之和
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
System.out.println(list.stream().map(item -> 2 * item).reduce(0, Integer::sum));
}
}
执行结果
1
2
3
+++++
3
4
5
6
7
+++++
3
4
5
6
7
8
+++++
30
5 stream相关接口
5.1 collect
接口
public class StreamTest {
public static void main(String[] args) {
//将Stream转为数组
Stream<String> stream = Stream.of("hello", "world", "hello world");
//其操作结果是将stream中的元素添加到list中
//第一个参数为容器提供者
//第二个参数为累加器
//第三个参数为组合器
List<String> resultList
= stream.collect(() -> new ArrayList<>(),
(list, item) -> list.add(item),
(list1, list2) -> list1.addAll(list2));
//通过方法引用,上述代码可简化为
//List<String> resultList = stream.collect(ArrayList::new, List::add, List::addAll);
System.out.println(resultList);
resultList.forEach(System.out :: println);
}
}
执行结果
hello
world
hello world
5.2 map
映射接口及reduce
压缩接口
public class StreamTest {
public static void main(String[] args) {
//对整型list中的数据*2并得到和
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
System.out.println(list.stream().map(item -> 2 * item).reduce(0, Integer::sum));
}
}
执行结果
30
示例中的map
接口起到的作用就是将流中的元素*2并返回
而reduce
接口起到的作用则是将流中的元素以累加求和的方式对流中的元素进行压缩并返回
5.3 多个接口整合使用
- 示例1
public class StreamTest {
public static void main(String[] args) {
List<String> list1 = Arrays.asList("hello", "world", "hello world", "test");
//将元素转为大写并输出
list1.stream().map(String::toUpperCase).forEach(System.out :: println);
System.out.println("+++++++");
//将元素的幂输出
List<Integer> list2 = Arrays.asList(1, 2, 3, 4, 5);
list2.stream().map(item -> item*item).forEach(System.out :: println);
System.out.println("+++++++");
//将多个list整合为一个list并元素的幂输出
Stream<List<Integer>> stream = Stream.of(Arrays.asList(1), Arrays.asList(2), Arrays.asList(3), Arrays.asList(4, 5, 6));
stream.flatMap(list -> list.stream().map(item -> item * item)).forEach(System.out :: println);
}
}
执行结果
HELLO
WORLD
HELLO WORLD
TEST
+++++++
1
4
9
16
25
+++++++
1
4
9
16
25
36
- 示例2
public class StreamTest {
public static void main(String[] args) {
//找出流中大于2的元素,然后将每个元素乘以2,然后忽略掉流中的前两个元素,然后再取流中的前两个元素,最后求出流中元素的和
Stream<Integer> stream = Stream.iterate(1, item -> item + 2).limit(10);
System.out.println(stream.filter(item -> item > 2).mapToInt(item -> item * 2).skip(2).limit(2).sum());
}
}
执行结果
32
-示例3
public class StreamTest {
public static void main(String[] args) {
//找出流中大于2的元素,然后将每个元素乘以2,然后忽略掉流中的前两个元素,然后再取流中的前两个元素,最后求出流中最小和最大的元素
Stream<Integer> stream = Stream.iterate(1, item -> item + 2).limit(10);
IntSummaryStatistics intSummaryStatistics = stream.filter(item -> item > 2).mapToInt(item -> item * 2).skip(2).limit(2).summaryStatistics();
System.out.println(intSummaryStatistics.getMax());
System.out.println(intSummaryStatistics.getMin());
}
}
执行结果
18
14
6 Stream陷阱
6.1 重复操作同一个流报错
如下代码
public class StreamTest {
public static void main(String[] args) {
//找出流中大于2的元素,然后将每个元素乘以2,然后忽略掉流中的前两个元素,然后再取流中的前两个元素,最后求出流中最小和最大的元素
Stream<Integer> stream = Stream.iterate(1, item -> item + 2).limit(10);
System.out.println(stream);
System.out.println(stream.filter(item -> item > 2));
System.out.println(stream.distinct());
}
}
执行结果
java.util.stream.SliceOps$1@15aeb7ab
java.util.stream.ReferencePipeline2@3feba861Exceptioninthread"main"java.lang.IllegalStateException:streamhasalreadybeenoperateduponorclosedatjava.util.stream.AbstractPipeline.<init>(AbstractPipeline.java:203)atjava.util.stream.ReferencePipeline.<init>(ReferencePipeline.java:94)atjava.util.stream.ReferencePipeline2@3feba861 Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed at java.util.stream.AbstractPipeline.<init>(AbstractPipeline.java:203) at java.util.stream.ReferencePipeline.<init>(ReferencePipeline.java:94) at java.util.stream.ReferencePipeline2@3feba861Exceptioninthread"main"java.lang.IllegalStateException:streamhasalreadybeenoperateduponorclosedatjava.util.stream.AbstractPipeline.<init>(AbstractPipeline.java:203)atjava.util.stream.ReferencePipeline.<init>(ReferencePipeline.java:94)atjava.util.stream.ReferencePipelineStatefulOp.(ReferencePipeline.java:647)
at java.util.stream.DistinctOps$1.(DistinctOps.java:56)
at java.util.stream.DistinctOps.makeRef(DistinctOps.java:55)
at java.util.stream.ReferencePipeline.distinct(ReferencePipeline.java:384)
at com.learn.jdk8.stream.StreamTest10.main(StreamTest10.java:20)
报错原因是
同一个stream流不能被连续操作两次
为了规避上述问题,代码需更改为
public class StreamTest {
public static void main(String[] args) {
Stream<Integer> stream = Stream.iterate(1, item -> item + 2).limit(10);
System.out.println(stream);
Stream<Integer> stream1 = stream.filter(item -> item > 2);
System.out.println(stream1);
Stream<Integer> stream2 = stream1.distinct();
System.out.println(stream2);
}
}
执行结果
java.util.stream.SliceOps$1@15aeb7ab
java.util.stream.ReferencePipeline$2@3feba861
java.util.stream.DistinctOps$1@5b480cf9
但是建议使用示例3
方法链的方式进行调用,而不是上述方式
6.2
如下代码
public class StreamTest {
public static void main(String[] args) {
IntStream.iterate(0, i -> (i + 1) % 2).distinct().limit(6).forEach(System.out :: println);
}
}
执行结果
0
1
虽然正常输出了,但是程序并未终止,原因就在于iterate
是无限循环的,若为在其后使用limit
方法对其进行限制,那么程序将会一直执行下去
所以应更改为
public class StreamTest {
public static void main(String[] args) {
IntStream.iterate(0, i -> (i + 1) % 2).limit(6).distinct().forEach(System.out :: println);
}
}
7 中间操作和终止操作的区别
- 示例1
public class StreamTest {
public static void main(String[] args) {
Stream<String> stream = Stream.of("hello", "world", "hello world");
//将首字母大写并输出
stream.map(item -> {
String result = item.substring(0, 1).toUpperCase() + item.substring((1));
System.out.println(result);
return result;
});
}
}
执行结果
- 示例2
public class StreamTest {
public static void main(String[] args) {
Stream<String> stream = Stream.of("hello", "world", "hello world");
//将首字母大写并输出
stream.map(item -> {
String result = item.substring(0, 1).toUpperCase() + item.substring((1));
System.out.println(result);
return result;
}).forEach(System.out :: println);
}
}
执行结果
Hello
Hello
World
World
Hello world
Hello world
两示例的区别仅在于末尾是否有终止操作方法
—forEach
,但执行结果却不同
原因是由于中间操作方法
是惰性求值,只有被终止操作方法
调用时,才会执行