stream介绍
java8引入了流(stream)处理,大大简化了集合操作。比如,遍历操作。一般会使用for-each循环,但是,在java8中,流处理就可以做到更加简洁。如下代码所示。
List<String> list = Lists.newArrayList("Chris Kate", "Bob Nana", "Tina Peter", "Bruce", "Jaker Alicia", null);
// 传统的使用forEach循环
int totalLen = 0;
for (String s : list) {
if (StringUtils.isEmpty(s)) {
continue;
}
totalLen += s.length();
}
System.out.println(totalLen);
// 使用java8流处理
long count = list.stream().filter(item -> StringUtils.isNotEmpty(item))
.map(item -> item.length())
.reduce((len1, len2) -> len1 + len2)
.orElse(0);
System.out.println(count);
在java8之前,对集合进行遍历,只能使用forEach循环。在java8之后,就可以使用流来对集合进行循环。
在上面的例子中,
1)流处理首先使用stream()创建一个指向该集合的流
2)使用filter()来过滤空指针
3)使用map()将每个元素映射到元素长度。映射之后,新的流里面存储的就是每个元素的长度了。
4)使用reduce()操作,将集合中元素的长度相加。
5)reduce()返回的是一个Optional对象,orElse(0)可以取出Optional对象里面的值;若里面的值为空,则返回0。
stream中的操作
怎么创建流
有多种方式可以创建一个流
- 从Collection集合创建 这通常是最常见的创建流的方式。
List<String> list = new ArrayList<>();
list.add("Bob");
list.add("Tina");
list.add("Chris");
Stream<T> stream = list.stream();
- 使用指定的元素来创建流
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9);
- 使用数组来创建流
String[] arr = new String[] { "a", "b", "c" };
Stream<T> streamOfArray = Arrays.stream(arr);
- 创建一个空的流
Stream<String> streamOfArray = Stream.empty();
- 使用Stream.builder来创建流(使用指定元素)
Stream.Builder<String> builder
= Stream.builder();
Stream<String> stream = builder.add("a")
.add("b")
.add("c")
.build();
上面说的是比较常见的创建流的方法,还有其他从Iterable来创建流的方法。
流的操作
创建了流之后,会对流进行操作,然后得到我们想要的流,或者转化为其他数据类型,比如,转化为集合,数组等。从使用的角度划分,流的操作可以分为中间操作和终止操作。中间操作相当于向流添加一个监听器,添加监听器时不会执行监听器。终止操作会对流进行迭代,执行监听器,返回结果。
一般,流的操作流程为:
1)从集合创建一个流
2)使用多个中间操作,得到相应的中间阶段的流。
3)使用一个终止操作,得到一个结果。结果可能为一个int类型的结果,或者一个集合,不会是一个Stream类型了。
中间操作
- Stream
filter(Predicate<? super T> predicate)
接受一个Predicate类型的lambda表达式,返回一个新的Stream。filter()会对每个元素进行迭代,当predicate返回true时,元素会保留在新的stream中;反之,元素不会保留在新的stream中。
List<String> list = Lists.newArrayList("Chris Kate", "Bob Nana", "Tina Peter", "Bruce", "Jaker Alicia", null);
list.stream().filter(item -> StringUtils.isNotEmpty(item)) // 过滤null元素
-
Stream map(Function<? super T, ? extends R> mapper)
接受一个mapper,mapper将流中的每个元素转换为相应的映射结果。返回一个新的流,新的流中的元素为转换后的结果。如下面的例子所示,先过滤null元素,然后使用 item -> item.length() 将流中的元素转换为字符串长度,加入新的流中。
Stream<Integer> newStream = list.stream().filter(item -> StringUtils.isNotEmpty(item))
.map(item -> item.length());
-
Stream flatMap(Function<? super T, ? extends Stream<? extends R>> mapper)
flatMap()的作用也是将流元素转化为另一个元素,但是,mapper对每个元素进行处理后,需要返回一个流对象。因此,在使用场景方面,mapper处理的元素往往是一个“复合”型的元素,比如,元素类型为字符串、list、set,这样的元素可以得到一个流。
List<String> list = Lists.newArrayList("Chris Kate", "Bob Nana", "Tina Peter", "Bruce", "Jaker Alicia", null);
list.stream().filter(item -> StringUtils.isNotEmpty(item))
.flatMap(item -> Arrays.stream(item.split(" ")));
上面的代码中,mapper将字符串流中的每个元素,使用空格分隔为数组,然后将数组转化为流返回。这样,这条语句执行之后(实际执行阶段在调用终止操作的时候),流中的元素就变成 (“Chris”, “ Kate”, “Bob”, “Nana”, “Tina”, “Peter”, “Bruce”, “Jaker”, “Alicia”)
至于为什么起名叫flatMap(),本人觉得效果类似于将每个“复合“型的元素给压扁了。使用flat很形象。
- vStream
distinct()
distinct()的作用是将流中的元素去重。元素进行相等比较的时候,是调用了它们的equals()方法。
- Stream
sorted(Comparator<? super T> comparator)
sorted(comparator)的作用是将流中的元素进行排序。排序的依据是comparator。比如,下面的操作将元素按照字符串长度从小到大排序。
list.stream().filter(item -> StringUtils.isNotEmpty(item))
.sorted((s1, s2) -> s1.length() - s2.length())
- Stream
limit(long maxSize)
limit(maxSize)限制流的最大元素个数。比如,下面的操作取字符串长度最小的两个。
list.stream().filter(item -> StringUtils.isNotEmpty(item))
.sorted((s1, s2) -> s1.length() - s2.length()).limit(2)
注意:中间操作只是添加一个监听器,不会实际执行监听器。监听器的执行时间在终止操作调用的时候。
中间操作可以分为两类:无状态(stateless)操作和有状态(stateful)操作
无状态操作是指,流里面每个元素的处理是相互独立的,不会受到前一个元素处理的影响。比如,filter(),map(), flatMap(),每个元素的处理不依赖于前一个元素的处理。
有状态操作是指,流里面每个元素的处理是依赖于其他元素的。比如,distinct()、sorted()、sorted(comparator)、limit()会依赖于流里面其他元素处理完,然后才可以执行去重操作、排序操作或者取前几个元素。
终止操作
终止操作会对流进行迭代,执行监听器,返回结果。返回的结果一般都是int等基本类型,或者一个集合类型,不会是一个Stream类型。因此,使用了终止操作之后,就没有办法继续对流进行下一步流操作了。
- boolean anyMatch(Predicate<? super T> predicate)
接受一个predicate。当流元素任意一个满足predicate条件判断时,则anyMatch()返回true;当流元素都不满足predicate条件判断时,anyMatch()返回false。
List<String> list = Lists.newArrayList("Chris Kate", "Bob Nana", "Tina Peter", "Bruce", "Jaker Alicia", null);
list.stream().filter(item -> StringUtils.isNotEmpty(item))
.anyMatch(item -> item.contains("Chris"));
上面的代码中,判断集合元素中是否有元素包含”Chris”。
- boolean allMatch(Predicate<? super T> predicate)
接受一个predicate。当流元素所有都满足predicate条件判断时,则allMatch()返回true;否则,返回false。
list.stream().filter(item -> StringUtils.isNotEmpty(item))
.allMatch(item -> !item.contains("dick"));
上面的操作,判断每个元素都不含有”dick”。
- boolean noneMatch(Predicate<? super T> predicate)
接受一个predicate。当流元素没有任何一个满足predicate条件判断时,则nonMatch()返回true;否则,返回false。
- Optional
findFirst()
获取流中的第一个元素。
Optional<String> first = list.stream().filter(item -> StringUtils.isNotEmpty(item))
.findFirst();
System.out.println(first.orElse(""));
- Optional
findAny()
获取流中的某个元素。
- <R, A> R collect(Collector<? super T, A, R> collector)
collect()用于将流中的元素收集到集合中。接受一个Collector类型的参数,Collector是java8提供的一个工具类,提供了很多获取lambda表达式(函数式操作)的方法。
list.stream().filter(item -> StringUtils.isNotEmpty(item))
.collect(Collectors.toList());
- long count()
统计流中的元素数量
- Optional
min(Comparator<? super T> comparator)
接收一个comparator比较器,返回流中最小的元素。
- Optional
max(Comparator<? super T> comparator)
接收一个comparator比较器,返回流中最大的元素。
- void forEach(Consumer<? super T> action)
对流中的每个元素进行迭代。action是一个操作,没有返回结果。
list.stream().filter(item -> StringUtils.isNotEmpty(item))
.forEach(item -> System.out.println(item));
- Optional
reduce(BinaryOperator accumulator)
reduce()会对流中的元素进行迭代,最终结果为一个元素。至于对元素进行怎么样的处理,则看传入的参数accumulator是什么样的操作。
下面的例子展示了计算所有非空元素的长度的总和。
Optional<Integer> reduce = list.stream().filter(item -> StringUtils.isNotEmpty(item))
.map(item -> item.length()).reduce((i1, i2) -> i1 + i2);
System.out.println(reduce.orElse(0));
再比如,下面的例子展示了选取list中元素长度最小的那个元素
Optional<String> reduce = list.stream().filter(item -> StringUtils.isNotEmpty(item))
.reduce((s1, s2) -> s1.length() < s2.length() ? s1 : s2);
System.out.println(reduce.orElse(""));
- Object[] toArray()
用于将流转化为一个数组。
终止操作可以分为短路操作(short-circuiting)和非短路操作。
短路操作是指,得到预期的结果之后,就会返回结果,不会接着处理剩下的元素了。比如,anyMatch()、allMatch()、noneMatch()都是短路操作。
非短路操作是指,处理完所有数据才可以拿到结果。比如,collect()、count()、forEach()、max()、min()、reduce()、toArray()都是非短路操作,处理完所有元素才能拿到结果。
stream vs foreach循环
遍历一个集合,对集合元素进行操作,既可以使用java8之前的for-each循环,也可以使用stream操作。如下面的代码所示。
List<String> list = Lists.newArrayList("Chris Kate", "Bob Nana", "Tina Peter", "Bruce", "Jaker Alicia", null);
// 传统的使用for-each循环
int totalLen = 0;
for (String s : list) {
if (StringUtils.isEmpty(s)) {
continue;
}
totalLen += s.length();
}
System.out.println(totalLen);
// 使用java8流处理
long count = list.stream().filter(item -> StringUtils.isNotEmpty(item))
.map(item -> item.length())
.reduce((len1, len2) -> len1 + len2)
.orElse(0);
System.out.println(count);
那么,它们各个方面有什么不同呢?知道它们的不同,可以让我们在合适的场景下选择合适的方式。

我们推荐
- 1)当lambda表达式简洁时,可以使用stream流处理;当lambda表达式比较复杂时,则使用传统的for-each循环。
- 2)在一般情况下,不必为了一丁点儿的性能改善来使用stream流处理。
- 3)当需要访问外部变量时,则不要使用stream流处理,因为lambda表达式中只能访问final或者effectively final的变量。
参考
Java Stream API
Streams 的幕后原理
原来你是这样的 Stream —— 浅析 Java Stream 实现原理
Java 8 Stream探秘
Java 8 Iterable.forEach() vs foreach loop
10 Ways to Create a Stream in Java