一,什么是Stream流
Stream 流是 Java 8 新提供给开发者的一组操作集合的 API,将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选、排序、聚合等。元素流在管道中经过中间操作(intermediate operation)的处理,最后由终端操作 (terminal operation) 得到前面处理的结果。Stream 流可以极大的提高开发效率,也可以使用它写出更加简洁明了的代码。我自从接触过 Stream 流之后,可以说对它爱不释手。本文将由浅及深带您体验 Stream 流的使用。那么就让我们从流的简单使用来开启体验之旅。
二,流的简单使用
创建流
有以下两种创建流的方式,第一种方式我们使用的会相对较多。
- 调用集合的
stream()
方法或者parallelStream()
方法创建流。 - Stream 类的静态
of()
方法创建流。
1. 创建 Stream 流
List< String> createStream = new ArrayList< String>();
// 顺序流
Stream< String> stream = createStream.stream();
// 并行流
Stream< String> parallelStream = createStream.parallelStream();
// of()方法创建
Stream< String> stringStream = Stream.of(
createStream.toArray(new String[createStream.size()]));
使用流
2 、,展示了如何使用 Stream 流筛选未及格学生名单:
清单 2. 使用 Stream 流筛选未及格学生名单
public static void streamImpl(List< Student> students) {
List< Student> filterStudent = students.stream()
.filter(one -> one.getScore() < 60).collect(Collectors.toList());
System.out.println(filterStudent);
}
而使用 Java 7 实现筛选未及格学生名单所需代码相对冗长。
三,流的基础知识
接下来您将了解 Stream 流的基础知识,这部分的内容将有助于您理解流的相关操作。
流的分类
Stream
流分为顺序流和并行流,所谓顺序流就是按照顺序对集合中的元素进行处理,而并行流则是使用多线程同时对集合中多个元素进行处理,所以在使用并行流的时候就要注意线程安全的问题了
终端操作和中间操作
终端操作会消费 Stream 流,并且会产生一个结果,比如 iterator()
和 spliterator()
。如果一个 Stream 流被消费过了,那它就不能被重用的。
中间操作会产生另一个流。需要注意的是中间操作不是立即发生的。而是当在中间操作创建的新流上执行完终端操作后,中间操作指定的操作才会发生。流的中间操作还分无状态操作和有状态操作两种。
- 在无状态操作中,在处理流中的元素时,会对当前的元素进行单独处理。比如,过滤操作,因为每个元素都是被单独进行处理的,所有它和流中的其它元素无关。
- 在有状态操作中,某个元素的处理可能依赖于其他元素。比如查找最小值,最大值,和排序,因为他们都依赖于其他的元素。
流接口
BaseStream 接口
从上面的 UML 图可以看出来 BaseStream
接口是 Stream 流最基础的接口,它提供了所有流都可以使用的基本功能。BaseStream
是一个泛型接口,它有两个类型参数 T
和 S
, 其中 T
指定了流中的元素的类型,S
指定了具体流的类型,由 <S extends BaseStream<T,S>>
可以知道 S
必须为 BaseStream
或 BaseStream
子类,换句话说,就是 S
必须是扩展自 BaseStream
的。BaseStream
继承了 AutoCloseable
接口,简化了关闭资源的操作,但是像平时我们操作的集合或数组,基本上都不会出现关闭流的情况。下面是 BaseStream
接口下定义的方法的相关解释:
Iterator<T> iterator()
:获取流的迭代器。Spliterator spliterator()
:获取流的spliterator
。boolean isParallel()
:判断一个流是否是并行流,如果是则返回true
,否则返回false
。S sequential()
:基于调用流返回一个顺序流,如果调用流本身就是顺序流,则返回其本身。S parallel()
:基于调用流,返回一个并行流,如果调用流本身就是并行流,则返回其本身。S unordered()
:基于调用流,返回一个无序流。S onClose(Runnable closeHandler)
:返回一个新流,closeHandler
指定了该流的关闭处理程序,当关闭该流时,将调用这个处理程序。void close()
:从AutoCloseable
继承来的,调用注册关闭处理程序,关闭调用流(很少会被使用到)。
清单 4 列举了由 BaseStream 接口派生出来的流接口,包括了 IntStream
,LongStream
,Stream
以及 DoubleStream
。其中 Stream 接口最为通用,本文的主要讲解对象也是它。
由 BaseStream
接口派生出的流接口
public interface IntStream extends BaseStream<、Intege、r, IntStream> {}
public interface LongStream extends BaseStream<Long, LongStream> {}
public interface DoubleStream extends BaseStream<Double, DoubleStream> {}
public interface Stream<T> extends BaseStream<T, Stream<T>> {}
Stream 接口
Stream filter(Predicate predicate)
:产生一个新流,其中包含调用流中满足predicate
指定的谓词元素,即筛选符合条件的元素后重新生成一个新的流。(中间操作)Stream map(Function mapper)
,产生一个新流,对调用流中的元素应用mapper
,新Stream
流中包含这些元素。(中间操作)IntStream mapToInt(ToIntFunction mapper)
:对调用流中元素应用mapper
,产生包含这些元素的一个新IntStream
流。(中间操作)Stream sorted(Comparator comparator)
:产生一个自然顺序排序或者指定排序条件的新流。(中间操作)void forEach(Consumer action)
:遍历了流中的元素。(终端操作)Optional min(Comparator comparator)
和Optional max(Comparator comparator)
:获得流中最大最小值,比较器可以由自己定义。(终端操作)boolean anyMatch(Predicate<? super T> predicate)
:判断Stream
流中是否有任何符合要求的元素,如果有则返回ture
,没有返回false
。(终端操作)Stream<T> distinct()
,去重操作,将Stream
流中的元素去重后,返回一个新的流。(中间操作)
收集操作相关 API
public interface Stream<T> extends BaseStream<T, Stream<T>> {
…
<R, A> R collect(Collector<? super T, A, R> collector);
…
}
其中 R
指定结果的类型,T
指定了调用流的元素类型。内部积累的类型由 A
指定。collector
是一个收集器,指定收集过程如何执行,collect()
方法是一个终端方法。一般情况我们只需要借助 Collectors
中的方法就可以完成收集操作。
Collectors
类是一个最终类,里面提供了大量的静态的收集器方法,借助他,我们基本可以实现各种复杂的功能了。
Collectors
public final class Collectors {
…
public static <T> Collector<T, ?, List<T>> toList() {
…
}
public static <T, K, U> Collector<T, ?, Map<K,U>> toMap(
Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper) {
…
}
…
}
Collectors
给我们提供了非常丰富的收集器,这里只列出来了 toList
和 toMap
两种,其他的可以参考 Collectors
类的源码。toList()
相信您在清单 14 中已经见到了,那么下面将展示如何将一个使用收集操作将一个 List
集合转为 Map
。
使收集操作将 List 转 Map
public static void list2Map() {
List<Student> students = initData();
Map<String, Double> collect = students.stream()
.collect(Collectors.toMap(one -> one.getName(),
one -> one.getScore()));
System.out.println(collect);
}
可以看到通过 Stream API 可以很方便地将一个 List
转成了 Map
,但是这里有一个地方需要注意。那就是在通过 Stream API 将 List
转成 Map
的时候我们需要确保 key
不会重复,否则转换的过程将会直接抛出异常。