Stream流(一)
Stream
流是Java8
的java.util.stream
包的接口,可以很方面的对集合等进行操作
1、创建Stream流
(1) 使用Stream
自带的方法
Stream.of
Stream.of
传入具体对象来生成Stream
流,如下:
1 | // Stream.of(T... values) |
Stream.iterate
Stream.iterate
生成的是一个无限流,需要传入两个参数,第一个是初始值,第二个参数是生成下一个值的方法,例子如下:
1 | // Stream.iterate(final T seed, final UnaryOperator<T> f) |
例子中,初始值是1,会将初始值传入n -> n + 1
中,得到下一个值是2。然后又把2传入到n -> n + 1
中,得到3…如此反复。因此流中元素会是1,2,3…
所以该方法生成的流是一个无限流。一般会接着limit
来获取流中的前几个元素
1 | // 获取流中的前5个元素,即流的元素为1,2,3,4,5 |
Stream.generate
Stream.generate
生成的也是一个无限流,需要传一个生成元素的方法,例子如下:
1 | // generate(Supplier<T> s) |
因此一般也会接着limit
来获取流中的前几个元素
(2)Collection
集合的stream()
方法
这是最常用的方法
1 | List<Integer> list = new ArrayList<>(); |
除了常用的
Stream
接口外,还有IntStream
、LongStream
、DoubleStream
,用法大致一样,不过里面增加了额外的方法
1
2
3
4
5
6
7
8
9
10
11 // IntStream
IntStream intStream = IntStream.of(1, 2, 3);
int intSum = intStream.sum();
// LongStream
LongStream longStream = LongStream.of(1L, 2L, 3L);
long longSum = longStream.sum();
// DoubleStream
DoubleStream doubleStream = DoubleStream.of(0.2, 0.3, 0.4);
double doubleSum = doubleStream.sum();
2、Stream方法分类
Stream
流的众多方法中,根据返回值类型可以分为中间操作和终端操作,返回值是Stream
的是中间操作,否则就是终端操作
值的注意的是,当且仅当存在终端操作时,中间操作操作才会被执行
例如以下的例子:
1 | public static void main(String[] args){ |
当运行时,控制台什么也没有输出
而当加上一个终端操作时:
1 | public static void main(String[] args){ |
控制台才会有输出:
1 | filter:1 |
3、Stream流的执行顺序
先来看一段代码,猜猜输出结果是什么
1 | public static void main(String[] args){ |
输出的顺序可能会让你很惊讶!你脑海里肯定会想,应该是先将所有 filter
前缀的字符串打印出来,接着才会打印 forEach
前缀的字符串,然而实际上的输出结果是:
1 | filter:1 |
也就是说,先是元素1执行了filter
,然后再执行forEach
,执行完毕后才是第二个元素。说明执行顺序是随着链条垂直移动的。
原因是出于性能的考虑。这样设计可以减少对每个元素的实际操作数。如以下例子:
1 | public static void main(String[] args){ |
由于数据流的链式调用是垂直执行的,所以只需要map
执行一次,filter
执行一次就行了。如果是水平执行的话,5个元素,map
要执行五次,filter
也要执行五次,这样执行起来操作次数多了。
4、Stream流的中间操作顺序
由上述我们得知,数据流的链式调用是垂直执行的,因此合理的中间操作顺序可以减少对元素的操作,从而优化性能,如下述的例子:
1 | public static void main(String[] args){ |
上面的例子中,map
执行了5次,filter
执行了5次,forEach
执行了2次
而当我们将中间操作的map
和filter
调换一下顺序:
1 | public static void main(String[] args){ |
map
执行了2次,filter
执行了5次,forEach
执行了2次
可以看到,map
的执行次数明显减少了,这种小技巧对于流中存在大量元素来说,是非常很有用的。
大部分数据流的链式调用是垂直执行的,但是也有例外,比如
sorted
,看如下例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 public static void main(String[] args){
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);
stream.sorted((i1, i2)->{
System.out.println("sorted:" + i1 + "--" + i2);
return i2.compareTo(i1);
}).filter(i -> {
System.out.println("filter:" + i);
return i > 3;
}).forEach(i -> {
System.out.println("forEach:" + i);
});
}
// 输出结果
// sorted:2--1
// sorted:3--2
// sorted:4--3
// sorted:5--4
// filter:5
// forEach:5
// filter:4
// forEach:4
// filter:3
// filter:2
// filter:1因为
sorted
是一个有状态的操作,要保证最后输出的结果是有序的,所以只能先将所有原先进行sorted
操作。对于上述,其实我们也能改变一下sorted
的位置,使之操作的元素减少:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 public static void main(String[] args){
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);
stream.filter(i -> {
System.out.println("filter:" + i);
return i > 3;
}).sorted((i1, i2)->{
System.out.println("sorted:" + i1 + "--" + i2);
return i2.compareTo(i1);
}).forEach(i -> {
System.out.println("forEach:" + i);
});
}
// 输出结果:
// filter:1
// filter:2
// filter:3
// filter:4
// filter:5
// sorted:5--4
// forEach:5
// forEach:4