创建 Stream
# 创建 Stream
要使用 Stream
,就必须先创建它。创建 Stream
有很多种方法,我们来一一介绍。
# Stream.of()
创建 Stream
最简单的方式是直接用 Stream.of()
静态方法,传入可变参数即创建了一个能输出确定元素的 Stream
:
Stream<String> stream = Stream.of("A", "B", "C", "D");
stream.forEach(System.out::print);
2
输出:
ABCD
forEach()
方法相当于内部循环调用,可传入符合 Consumer 接口的 void accept(T t)的方法引用,例如 print。
这种方式基本上没啥实质性用途,但测试的时候很方便。
# 基于数组或 Collection
第二种创建 Stream
的方法是基于一个数组或者 Collection
,这样该 Stream
输出的元素就是数组或者 Collection
持有的元素:
- 把数组变成
Stream
使用Arrays.stream()
方法。 - 对于
Collection
(List
、Set
、Queue
等),直接调用stream()
方法就可以获得Stream
。
Stream<String> stream2 = Arrays.stream(new String[]{"A", "B", "C", "D"});
Stream<String> stream3 = Arrays.asList("A", "B", "C", "D").stream();
2
上述创建 Stream
的方法都是把一个现有的序列变为 Stream
,它的元素是固定的。
# 其他方法
创建 Stream
的第三种方法是通过一些 API 提供的接口,直接获得 Stream
。
例如,Files
类的 lines()
方法可以把一个文件变成一个 Stream
,每个元素代表文件的一行内容:
try (Stream<String> lines = Files.lines(Paths.get("/path/to/file.txt"))) {
...
}
2
3
此方法对于按行遍历文本文件十分有用。
另外,正则表达式的 Pattern
对象有一个 splitAsStream()
方法,可以直接把一个长字符串分割成 Stream
序列而不是数组:
Pattern p = Pattern.compile("\\s+");
Stream<String> s = p.splitAsStream("The quick brown fox jumps over the lazy dog");
s.forEach(System.out::println);
2
3
# 基本类型
因为 Java 的范型不支持基本类型,所以我们无法用 Stream<int>
这样的类型,会发生编译错误。为了保存 int
,只能使用 Stream<Integer>
,但这样会产生频繁的装箱、拆箱操作。为了提高效率,Java 标准库提供了 IntStream
、LongStream
和 DoubleStream
这三种使用基本类型的 Stream
,它们的使用方法和范型 Stream
没有大的区别,设计这三个 Stream
的目的是提高运行效率:
// 将int[]数组变为IntStream:
IntStream is = Arrays.stream(new int[] { 1, 2, 3 });
// 将Stream<String>转换为LongStream:
LongStream ls = List.of("1", "2", "3").stream().mapToLong(Long::parseLong);
2
3
4
5
# 基于 Supplier
创建 Stream
还可以通过 Stream.generate()
方法,它需要传入一个 Supplier
对象:
Stream<String> s = Stream.generate(Supplier<String> sp);
基于 Supplier
创建的 Stream
会不断调用 Supplier.get()
方法来不断产生下一个元素,这种 Stream
保存的不是元素,而是算法,它可以用来表示无限序列。
例如,我们编写一个能不断生成自然数的 Supplier
,它的代码非常简单,每次调用 get()
方法,就生成下一个自然数:
import java.util.function.*;
import java.util.stream.*;
public class Main {
public static void main(String[] args) {
Stream<Integer> natual = Stream.generate(new NatualSupplier());
// 注意:无限序列必须先变成有限序列再打印:
natual.limit(20).forEach(System.out::println);
}
}
class NatualSupplier implements Supplier<Integer> {
int n = 0;
public Integer get() {
n++;
return n;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
上述代码我们用一个 Supplier<Integer>
模拟了一个无限序列(当然受 int
范围限制不是真的无限大)。如果用 List
表示,即便在 int
范围内,也会占用巨大的内存,而 Stream
几乎不占用空间,因为每个元素都是实时计算出来的,用的时候再算。
对于无限序列,如果直接调用 forEach()
或者 count()
这些最终求值操作,会进入死循环,因为永远无法计算完这个序列,所以正确的方法是先把无限序列变成有限序列,例如,用 limit()
方法可以截取前面若干个元素,这样就变成了一个有限序列,对这个有限序列调用 forEach()
或者 count()
操作就没有问题。
# 练习
编写一个能输出斐波拉契数列(Fibonacci)的 LongStream
:
1, 1, 2, 3, 5, 8, 13, 21, 34, ...
提示:使用 Supplier