目录
1.什么是流
2.构建流
2.1由值构建流
2.2由数组创建流
2.3由文件生成流
2.4由函数生成流:创建无限流
2.4.1迭代
2.4.2生成
3.流操作
3.1中间操作
3.2终端操作
3.3 实例
4.收集数据
4.1归约
4.1.1求和
4.1.2最大值:
4.1.3最小值:
4.2汇总
4.2.1常用汇总方法
4.2.2字符串连接
Java8对流的定义就是“从支持数据处理操作的源生成的元素序列”。
和迭代器类似,流只能遍历一次。遍历完之后,我们就说这个流已经被消费掉了。你可以从原始数据源那里再获得一个新的流来重新遍历一遍,就像迭代器一样(这里假设它是集合之类的可重复的源,如果是I/O通道就没戏了)。例如,以下代码会抛出一个异常,说流已被消费掉了:
List title = Arrays.asList("Java8", "In", "Action");
Stream s = title.stream();
s.forEach(System.out::println);
s.forEach(System.out::println);
流不仅可以从集合创建,也可从值、数组、文件以及 iterate 与 generate 等特定方法创建,还可以构建没有固定大小的无限流
你可以使用静态方法 Stream.of ,通过显式值创建一个流。它可以接受任意数量的参数。例如,以下代码直接使用 Stream.of 创建了一个字符串流。然后,你可以将字符串转换为大写,再
一个个打印出来:
Stream stream = Stream.of("Java 8 ", "Lambdas ", "In ", "Action");
stream.map(String::toUpperCase).forEach(System.out::println);
你可以使用 empty 得到一个空流,如下所示:
Stream emptyStream = Stream.empty();
你可以使用静态方法 Arrays.stream 从数组创建一个流。它接受一个数组作为参数。例如,你可以将一个原始类型 int 的数组转换成一个 IntStream ,如下所示:
int[] numbers = {2, 3, 5, 7, 11, 13};
int sum = Arrays.stream(numbers).sum(); //结果是41
Java中用于处理文件等I/O操作的NIO API(非阻塞 I/O)已更新,以便利用Stream API。java.nio.file.Files 中的很多静态方法都会返回一个流。例如,一个很有用的方法是Files.lines ,它会返回一个由指定文件中的各行构成的字符串流。使用你迄今所学的内容,你可以用这个方法看看一个文件中有多少各不相同的词:
long uniqueWords = 0;
try(Stream lines =Files.lines(Paths.get("data.txt"), Charset.defaultCharset())){
uniqueWords = lines.flatMap(line -> Arrays.stream(line.split(" ")))
.distinct()
.count();
}catch(IOException e){
}
//这里使用的是try-with-resources语句优雅的关闭资源的方式,java7的新特性
你可以使用 Files.lines 得到一个流,其中的每个元素都是给定文件中的一行。然后,你可以对 line 调用 split 方法将行拆分成单词。应该注意的是,你该如何使用 flatMap 产生一个扁平的单词流,而不是给每一行生成一个单词流。最后,把 distinct 和 count 方法链接起来,数数流中有多少各不相同的单词。
Stream API提供了两个静态方法来从函数生成流: Stream.iterate 和 Stream.generate 。这两个操作可以创建所谓的无限流:不像从固定集合创建的流那样有固定大小的流。由 iterate和 generate 产生的流会用给定的函数按需创建值,因此可以无穷无尽地计算下去!一般来说,应该使用 limit(n) 来对这种流加以限制,以避免打印无穷多个值。
我们先来看一个 iterate 的简单例子,然后再解释:
Stream.iterate(0, n -> n + 2)
.limit(10)
.forEach(System.out::println);
iterate 方法接受一个初始值(在这里是 0 ),还有一个依次应用在每个产生的新值上的Lambda( UnaryOperator
与 iterate 方法类似, generate 方法也可让你按需生成一个无限流。但 generate 不是依次对每个新生成的值应用函数的。它接受一个 Supplier
Stream.generate(Math::random)
.limit(5)
.forEach(System.out::println);
这段代码将生成一个流,其中有五个0到1之间的随机双精度数。例如,运行一次得到了下面的结果:
0.9410810294106129
0.6586270755634592
0.9592859117266873
0.13743396659487006
0.3942776037651241
Math.Random 静态方法被用作新值生成器。同样,你可以用 limit 方法显式限制流的大小,否则流将会无限长。
IntStream 的 generate 方法会接受一个 IntSupplier ,而不是 Supplier
IntStream ones = IntStream.generate(() -> 1);
Lambda允许你创建函数式接口的实例,只要直接内联提供方法的实现就可以。你也可以像下面这样,通过实现 IntSupplier 接口中定义的 getAsInt 方法显式传递一个对象:
IntStream twos = IntStream.generate(new IntSupplier(){
public int getAsInt(){
return 2;
}
});
generate 方法将使用给定的供应源,并反复调用 getAsInt 方法,而这个方法总是返回 2 。
斐波纳契数列是著名的经典编程练习。下面这个数列就是斐波纳契数列的一部分:0, 1, 1,2, 3, 5, 8, 13, 21, 34, 55…数列中开始的两个数字是0和1,后续的每个数字都是前两个数字之和。斐波纳契元组序列与此类似,是数列中数字和其后续数字组成的元组构成的序列:(0, 1),(1, 1), (1, 2), (2, 3), (3, 5), (5, 8), (8, 13), (13, 21) …,下面的代码就是如何创建一个在调用时返回下一个斐波纳契项的IntSupplier :
IntSupplier fib = new IntSupplier(){
private int previous = 0;
private int current = 1;
public int getAsInt(){
int oldPrevious = this.previous;
int nextValue = this.previous + this.current;
this.previous = this.current;
this.current = nextValue;
return oldPrevious;
}
};
IntStream.generate(fib).limit(10).forEach(System.out::println);
流的使用一般包括三件事:
操作 | 返回类型 | 使用的类型/函数式接口 | 函数描述符 | 解释 |
filter | Stream |
Predicate |
T->boolean | 根据谓词删选之后,返回筛选之后的流 |
map | Stream |
Function |
T->R | 接受一个函数作为参数。这个函数会被应用到每个元素上,并将其映射成一个新的元素 |
flatmap | Stream |
Function |
T->Stream |
把一个流中的每个值都换成另一个流,然后把所有的流连接起来成为一个流 |
distinct | Stream |
返回一个元素各异(根据流所生成元素的 hashCode 和 equals 方法实现)的流 |
||
sorted | Stream |
Comparator |
int compare(T o1, T o2) | 返回一个排序后的流,可以在传入条件的时候使用reversed()方法反转排序结果 |
limit | Stream |
long | 截短流,返回前 n 个元素的流 | |
skip | Stream |
long | 跳过流,返回一个扔掉了前 n 个元素的流 |
操作 | 返回类型 | 使用的类型/函数式接口 | 函数描述符 | 解释 |
anyMatch | boolean | Predicate |
T -> boolean | 检查谓词是否至少匹配一个元素 |
noneMatch | boolean | Predicate |
T -> boolean | 检查流中没有任何元素与给定的谓词匹配 |
allMatch | boolean | Predicate |
T -> boolean | 检查谓词是否匹配所有元素 |
findAny | Optional |
返回当前流中的任意元素 | ||
findFirst | Optional |
返回流中的第一个元素 | ||
forEach | void | Consumer |
T -> void | 消费流中的每一个元素(无返回值) |
collect | R | Collector |
接受各种方案作为参数,并将流中的元素累积成为一个汇总结果的操作 | |
reduce | Optional |
BinaryOperator |
(T, T) -> T | 把一个流中的元素组合起来,归约成一个值 |
count | long | 统计流中元素的个数 |
String[] words = {"Hello", "World"};
List uniqueCharacters =
Arrays.stream(words)
.map(w -> w.split(""))
.flatMap(Arrays::stream)
.distinct()
.collect(Collectors.toList());
uniqueCharacters.stream().forEach(System.out::println);
使用 flatMap 方法的效果是,各个数组并不是分别映射成一个流,而是映射成流的内容。所有使用 map(Arrays::stream) 时生成的单个流都被合并起来,即扁平化为一个流。
使用 flatMap 方法的效果是,各个数组并不是分别映射成一个流,而是映射成流的内容。所有使用 map(Arrays::stream) 时生成的单个流都被合并起来,即扁平化为一个流。
规约的reduce 接受两个参数:
int sum = numbers.stream().reduce(0, (a, b) -> a + b);
numbers 中的每个元素都用加法运算符反复迭代来得到结果。通过反复使用加法,你把一个数字列表归约成了一个数字。首先, 0 作为Lambda( a )的第一个参数,从流中获得 4 作为第二个参数( b )。 0 + 4 得到 4 ,它成了新的累积值。然后再用累积值和流中下一个元素 5 调用Lambda,产生新的累积值 9 。接下来,再用累积值和下一个元素 3调用Lambda,得到 12 。最后,用 12 和流中最后一个元素 9 调用Lambda,得到最终结果 21 。
无初始值
reduce 还有一个重载的变体,它不接受初始值,但是会返回一个 Optional 对象:
Optional sum = numbers.stream().reduce((a, b) -> (a + b));
考虑流中没有任何元素的情况。 reduce 操作无法返回其和,因为它没有初始值。这就是为什么结果被包裹在一个 Optional 对象里,以表明和可能不存在。
一般的reduce操作总结:
Optional
Optional
等价于Optional
Collectors.summingInt 。它可接受一个把对象映射为求和所需 int 的函数,并返回一个收集器;该收集器在传递给普通的 collect 方法后即执行我们需要的汇总操作。汇总还有其他操作,如 Collectors.averagingInt,averagingLong,averagingDouble 可以计算数值的平均数。
但有时候我们需要找到元素数值属性的最大值和最小值,以及计算其总和和平均值。那么只需一次操作就可以完成。在这种情况下,你可以使用 summarizingInt 工厂方法返回的收集器。
IntSummaryStatistics menuStatistics =
menu.stream().collect(summarizingInt(Dish::getCalories));
这个收集器会把所有这些信息收集到一个叫作 IntSummaryStatistics 的类里,它提供了方便的取值(getter)方法来访问结果。打印 menuStatisticobject 会得到以下输出:
IntSummaryStatistics{count=9, sum=4300, min=120,
average=477.777778, max=800}
同样,相应的 summarizingLong 和 summarizingDouble 工厂方法有相关的 LongSummaryStatistics 和 DoubleSummaryStatistics 类型,适用于收集的属性是原始类型 long 或double 的情况。
joining 工厂方法返回的收集器会把对流中每一个对象应用 toString 方法得到的所有字符串连接成一个字符串。
String[] words = {"Hello", "World"};
String collect = Arrays.stream(words).collect(Collectors.joining("-"));
System.out.println(collect);
最后一个方法是最详细的,三个参数分别为分隔符,前缀和后缀。