Stream流是Java8java.util.stream包的接口,可以很方面的对集合等进行操作

1、创建Stream流

(1) 使用Stream自带的方法

Stream.of

Stream.of传入具体对象来生成Stream流,如下:

1
2
// Stream.of(T... values)
Stream<Integer> stream = Stream.of(1, 2, 3);

Stream.iterate

Stream.iterate生成的是一个无限流,需要传入两个参数,第一个是初始值,第二个参数是生成下一个值的方法,例子如下:

1
2
// Stream.iterate(final T seed, final UnaryOperator<T> f)
Stream<Integer> iterate = Stream.iterate(1, n -> n + 1);

例子中,初始值是1,会将初始值传入n -> n + 1中,得到下一个值是2。然后又把2传入到n -> n + 1中,得到3…如此反复。因此流中元素会是1,2,3…

所以该方法生成的流是一个无限流。一般会接着limit来获取流中的前几个元素

1
2
// 获取流中的前5个元素,即流的元素为1,2,3,4,5
Stream<Integer> iterate = Stream.iterate(1, n -> n + 1).limit(5);

Stream.generate

Stream.generate生成的也是一个无限流,需要传一个生成元素的方法,例子如下:

1
2
3
4
5
// generate(Supplier<T> s)
// 生成无限个1
Stream<Integer> generate1 = Stream.generate(() -> 1);
// 生成无限个100内的随机数
Stream<Integer> generate2 = Stream.generate(() -> new Random().nextInt(100));

因此一般也会接着limit来获取流中的前几个元素

(2)Collection集合的stream()方法

这是最常用的方法

1
2
3
4
5
List<Integer> list = new ArrayList<>();
Stream<Integer> stream = list.stream();

Set<String> set = new HashSet<>();
Stream<String> stringStream = set.stream();

除了常用的Stream接口外,还有IntStreamLongStreamDoubleStream,用法大致一样,不过里面增加了额外的方法

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
2
3
4
5
6
7
8
9
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 > 0;
});

}

当运行时,控制台什么也没有输出

而当加上一个终端操作时:

1
2
3
4
5
6
7
8
9
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 > 0;
}).count();

}

控制台才会有输出:

1
2
3
4
5
filter:1
filter:2
filter:3
filter:4
filter:5

3、Stream流的执行顺序

先来看一段代码,猜猜输出结果是什么

1
2
3
4
5
6
7
8
9
10
11
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 > 0;
}).forEach(i->{
System.out.println("forEach:" + i);
});

}

输出的顺序可能会让你很惊讶!你脑海里肯定会想,应该是先将所有 filter 前缀的字符串打印出来,接着才会打印 forEach 前缀的字符串,然而实际上的输出结果是:

1
2
3
4
5
6
7
8
9
10
filter:1
forEach:1
filter:2
forEach:2
filter:3
forEach:3
filter:4
forEach:4
filter:5
forEach:5

也就是说,先是元素1执行了filter,然后再执行forEach,执行完毕后才是第二个元素。说明执行顺序是随着链条垂直移动的。

原因是出于性能的考虑。这样设计可以减少对每个元素的实际操作数。如以下例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static void main(String[] args){

Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);
stream.map(i->{
System.out.println("map:" + i);
return i + 1;
}).filter(i -> {
System.out.println("filter:" + i);
return i > 0;
}).findFirst();

}
// 结果:
// map:1
// filter:2

由于数据流的链式调用是垂直执行的,所以只需要map执行一次,filter执行一次就行了。如果是水平执行的话,5个元素,map要执行五次,filter也要执行五次,这样执行起来操作次数多了。

4、Stream流的中间操作顺序

由上述我们得知,数据流的链式调用是垂直执行的,因此合理的中间操作顺序可以减少对元素的操作,从而优化性能,如下述的例子:

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
27
28
public static void main(String[] args){

Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);
stream.map(i->{
System.out.println("map:" + i);
return i;
}).filter(i -> {
System.out.println("filter:" + i);
return i > 3;
}).forEach(i -> {
System.out.println("forEach:" + i);
});

}

// 输出结果
// map:1
// filter:1
// map:2
// filter:2
// map:3
// filter:3
// map:4
// filter:4
// forEach:4
// map:5
// filter:5
// forEach:5

上面的例子中,map执行了5次,filter执行了5次,forEach执行了2次

而当我们将中间操作的mapfilter调换一下顺序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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;
}).map(i->{
System.out.println("map:" + i);
return i;
}).forEach(i -> {
System.out.println("forEach:" + i);
});

}
// 输出结果
// filter:1
// filter:2
// filter:3
// filter:4
// map:4
// forEach:4
// filter:5
// map:5
// forEach:5

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