- 由浅入深的学习推荐阅读图书《Java 8 实战_高清中文版》
- 学习流之前,需要有一定的Lambda表达式的基础知识,可阅读《JDK 1.8 新特性之Lambda表达式》
一、前言
流是 Java 8 的新成员,它允许你以声明式方式处理数据集合(通过查询语句来表达,而不是临时编写一个实现)。此外,流还可以透明地并行处理,你无需写任何多线程代码了!
流的简短定义:从支持数据处理操作的源生成的元素序列。
- 元素序列:就像集合一样,流也提供了一个接口,可以访问特定元素类型的一组有序值。因为集合是数据结构,所以它的主要目的是以特定的时间/空间复杂度存储和访问元素(如ArrayList 与 LinkedList)。但流的目的在于表达计算。集合讲的是数据,流讲的是计算。
- 源:流会使用一个提供数据的源,如集合、数组或输入/输出资源。 请注意,从有序集合生成流时会保留原有的顺序。由列表生成的流,其元素顺序与列表一致。
- 数据处理操作:流的数据处理功能支持类似于数据库的操作,以及函数式编程语言中的常用操作,如
filter
、map
、reduce
、find
、match
、sort
等。流操作可以顺序执行,也可并行执行。
此外,流操作有两个重要的特点:
- 流水线:很多流操作本身会返回一个流,这样多个操作就可以链接起来,形成一个大的流水线。
- 内部迭代:与使用迭代器显式迭代的集合不同,流的迭代操作是在背后进行的。
你可以往集合里加东西或者删东西,但是不管什么时候,集合中的每个元素都是放在内存里的。相比之下,流则是在概念上固定的数据结构(你不能添加或删除元素),其元素则是按需计算的。从另一个角度来说,流就像是一个延迟创建的集合:只有在消费者要求的时候才会计算值(用管理学的话说这就是需求驱动,甚至是实时制造)。与此相反,集合则是急切创建的(供应商驱动:先把仓库装满,再开始卖,就像那些昙花一现的圣诞新玩意儿一样)。
请注意,和迭代器类似,流只能遍历一次。遍历完之后,我们就说这个流已经被消费掉了。你可以从原始数据源那里再获得一个新的流来重新遍历一遍,就像迭代器一样(这里假设它是集合之类的可重复的源,如果是I/O通道就没戏了)。
java.util.stream.Stream
中的Stream接口定义了许多操作,它们可以分为两大类:
- 中间操作:诸如
filter
或sorted
等中间操作会返回另一个流。这让多个操作可以连接起来形成一个查询。重要的是,除非流水线上触发一个终端操作,否则中间操作不会执行任何处理 – 它们很懒。这是因为中间操作一般都可以合并起来,在终端操作时一次性全部处理。 - 终端操作:终端操作会从流的流水线生成结果。其结果是任何不是流的值,比如
List
、Integer
,甚至void
。
二、构建流
2.1 由值创建流
使用Stream
的静态方法of
,通过显式值创建一个流。
static <T> Stream<T> of(T... values)
示例:
Stream<String> stream = Stream.of("A","B","C");
stream.map(String::toLowerCase).forEach(System.out::print);
2.2 由数组创建流
使用Arrays
的静态方法stream
可以从数组创建一个流。
public static <T> Stream<T> stream(T[] array)
public static <T> Stream<T> stream(T[] array,int startInclusive,int endExclusive)
注意:真多基本数据类型,都提供了对应的原始类型流特化的方法重载,如果是基本数据类型的的数组则会转换为对应的原始类型特化流,即IntStream
,DoubleStream
和 LongStream
。
int[] numbers = {
2, 3, 5, 7, 11, 13};
IntStream intStre= Arrays.stream(numbers);
int sum = intStre.sum();//总和是41
2.3 由集合创建流
Collection
接口增加了默认方法stream
和 parallelStream()
方法,因此任何集合的实现都可以调用这两个方法由集合创建流。区别在于,后者创建的是一个并行流。
default Stream<E> stream()
default Stream<E> parallelStream()
2.4 由文件生成流
Java 中用于处理文件等 I/O 操作的 NIO API(非阻塞 I/O)已更新,以便利用 Stream API。java.nio.file.Files
中的很多静态方法都会返回一个流。
例如,一个很有用的方法是Files.lines
,它会返回一个由指定文件中的各行构成的字符串流。使用你迄今所学的内容,你可以用这个方法看看一个文件中有多少各不相同的词:
long uniqueWords=0;
try(Stream<String> lines=Files.lines(Paths.get("data.txt"), Charset.defaultCharset())){
//流会自动关闭
uniqueWords = lines.flatMap(line -> Arrays.stream(line.split(" ")))//生成单词流
.distinct()//合并重复项
.count();//统计各个不相同的单词的个数
}catch (IOException e) {
}
我们可以使用Files.lines
得到一个流,其中的每个元素都是给定文件中的一行。然后,你可以对line
调用split
方法将行拆分成单词。应该注意的是,你该如何使用flatMap
产生一个扁平的单词流,而不是给每一行生成一个单词流。最后,把distinct
和count
方法链接起来,数数流中有多少各不相同的单词。
2.5 由函数生成流:创建无限流
Stream API 提供了两个静态方法来从函数生成流: Stream.iterate
和 Stream.generate
。这两个操作可以创建所谓的无限流:不像从固定集合创建的流那样有固定大小的流。由iterate
和generate
产生的流会用给定的函数按需创建值,因此可以无穷无尽地计算下去!一般来说,应该使用 limit(n)
来对这种流加以限制,以避免打印无穷多个值。
迭代
使用Stream
的静态方法iterate
,可以创建一个无限流。
static <T> Stream<T> iterate(T seed,UnaryOperator<T> f)
示例:
Stream.iterate(0, n -> n + 2).limit(10).forEach(System.out::println);
iterate
方法接受一个初始值(在这里是0
),还有一个依次应用在每个产生的新值上的Lambda(UnaryOperator<t>
类型)。这里,我们使用Lambda n -> n + 2
,返回的是前一个元素加上2
。因此, iterate
方法生成了一个所有正偶