上篇文章讲了Stream的常用方法,可以先回顾一下:图解Java8 Stream,真香
如果你没听过Stream的中间操作顺序,你应该看看这篇文章。
垂直执行:map和filter
先看下面一个例子,找出流中”b”的字符串,并将其转化为大写,包含两个中间操作 map 和 filter 以及结束操作forEach。
Stream.of("a", "b", "c", "d", "e")
.map(str -> {
System.out.println("map: \t" + str);
return str.toUpperCase();
})
.filter(str -> {
System.out.println("filter: " + str);
return str.equals("B");
})
.forEach(str -> {
System.out.println("forEach: " + str);
});
// 控制台输出
map: a
filter: A
map: b
filter: B
forEach: B
map: c
filter: C
map: d
filter: D
map: e
filter: E
由输出结果可以看出,map/filter/forEach 是垂直执行,map和filter执行了5次,forEach执行了1次。
如果我们改变操作顺序,filter方法最先执行,将大大减少执行的次数。
Stream.of("a", "b", "c", "d", "e")
.filter(str -> {
System.out.println("filter: " + str);
return str.equals("b");
})
.map(str -> {
System.out.println("map: \t" + str);
return str.toUpperCase();
})
.forEach(str -> {
System.out.println("forEach: " + str);
});
// 控制台输出
filter: a
filter: b
map: b
forEach: B
filter: c
filter: d
filter: e
filter还是执行了5次,forEach执行了1次,但map降为了1次,也就是说符合filter的才会执行map操作,这种技巧在Stream流中有大量元素时,执行的更快。
水平执行:sorted
接下来,我们看下sorted排序操作,现学现卖,咱们把filter放在了map操作之前:
Stream.of("d", "b", "c", "a", "e")
.sorted((str1, str2) -> {
System.out.println("sorted: " + str1 + ", " + str2);
return str1.compareTo(str2);
})
.filter(str -> {
System.out.println("filter: " + str);
return str.equals("b");
})
.map(str -> {
System.out.println("map: \t" + str);
return str.toUpperCase();
})
.forEach(str -> {
System.out.println("forEach: " + str);
});
// 控制台输出
sorted: b, d
sorted: c, b
sorted: c, d
sorted: c, b
sorted: a, c
sorted: a, b
sorted: e, c
sorted: e, d
filter: a
filter: b
map: b
forEach: B
filter: c
filter: d
filter: e
这时候发现,sorted操作是水平执行的,会对流中所有的元素进行排序,总共执行了8次。
我们再一次更改操作顺序,来尝试优化性能:
Stream.of("d", "b", "c", "a", "e")
.filter(str -> {
System.out.println("filter: " + str);
return str.equals("b");
})
.sorted((str1, str2) -> {
System.out.println("sorted: " + str1 + ", " + str2);
return str1.compareTo(str2);
})
.map(str -> {
System.out.println("map: \t" + str);
return str.toUpperCase();
})
.forEach(str -> {
System.out.println("forEach: " + str);
});
// 控制台输出
filter: d
filter: b
filter: c
filter: a
filter: e
map: b
forEach: B
这次发现,sorted方法并没有被调用,因为被filter过滤之后,流中只剩下一个元素,也就不需要执行排序操作了。
而且,sorted对filter和map这种垂直执行的方法,具有截断作用,也就是说sorted前的中间操作,需要完全执行,形成一个完整的Stream流,交给sorted排序。
混合使用
举个例子,比如把”b”和”d”过滤出来,转为大写,并排序:
Stream.of("d", "b", "c", "a", "e")
.filter(str -> {
System.out.println("filter: " + str);
return str.equals("b") || str.equals("d");
})
.map(str -> {
System.out.println("map: \t" + str);
return str.toUpperCase();
})
.sorted((str1, str2) -> {
System.out.println("sorted: " + str1 + ", " + str2);
return str1.compareTo(str2);
})
.forEach(str -> {
System.out.println("forEach: " + str);
});
// 控制台输出
filter: d
map: d
filter: b
map: b
filter: c
filter: a
filter: e
sorted: B, D
forEach: B
forEach: D
因为sorted的截断作用,先垂直执行filter和map,然后水平执行sorted,最后垂直执行forEach。
小结
在编写复杂的Stream方法链时,一般应该把filter操作放到最前边,以减少后续操作的压力,这一点在Stream流中有大量元素时更为明显。
而且,你最好把上边的代码自己运行下,看看输出结果,你会理解的更深刻,下一节,咱们聊聊Stream的高级操作。
留个思考题:Stream.sorted方法,底层的排序算法是什么?欢迎留言区讨论。