Java 8 Stream API:第 2 部分
介绍
在本 Java 8 Stream API 教程的第一部分中,我们介绍了什么是流以及流的一些最常见的操作。
闲话少说,我们继续介绍以函数式风格编写流的方法。之后,我们再看看并行流。
地图
map()用于转换流元素的值或类型:
<R> Stream<R> map(Function<? super T,? extends R> mapper)
IntStream mapToInt(ToIntFunction<? super T> mapper)
LongStream mapToLong(ToLongFunction<? super T> mapper)
DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper)
如您所见,map()采用函数将类型T的流的元素转换为类型R ,并返回该类型R的流:
Stream.of('a', 'b', 'c', 'd', 'e')
.map(c -> (int)c)
.forEach(i -> System.out.format("%d ", i));
输出:
97 98 99 100 101
有转换为原始类型的版本。例如:
IntStream.of(100, 110, 120, 130 ,140)
.mapToDouble(i -> i/3.0)
.forEach(i -> System.out.format("%.2f ", i));
将会输出:
33.33 36.67 40.00 43.33 46.67
平面地图
flatMap()用于将流的元素展平(或组合)为一个(新)流:
<R> Stream<R> flatMap(Function<? super T,
? extends Stream<? extends R>> mapper)
DoubleStream flatMapToDouble(Function<? super T,
? extends DoubleStream> mapper)
IntStream flatMapToInt(Function<? super T,
? extends IntStream> mapper)
LongStream flatMapToLong(Function<? super T,
? extends LongStream> mapper)
从它的签名(以及原始版本的签名)我们可以看出,与返回单个值的map()相比, flatMap()必须返回一个 Stream。如果flatMap()映射到null,则返回值将是一个空流,而不是null本身。
让我们看看它是如何工作的。假设我们有一个包含字符列表的流:
List<Character> aToD = Arrays.asList('a', 'b', 'c', 'd');
List<Character> eToG = Arrays.asList('e', 'f', 'g');
Stream<List<Character>> stream = Stream.of(aToD, eToG);
我们希望将所有字符转换为它们的 int 表示形式。请注意,通过下面的代码,我们不能再使用map() ; c 表示List<Character>类型的对象,而不是Character:
stream .map(c -> (int)c)
相反,我们需要将列表的元素放入一个流中,然后将每个字符转换为 int。幸运的是,我们有flatMap()将列表元素组合成单个 Stream 对象:
stream
.flatMap(l -> l.stream())
.map(c -> (int)c)
.forEach(i -> System.out.format("%d ", i));
输出以下内容:
97 98 99 100 101 102 103
flatMap()返回一个流,而map()返回一个元素。
在flatMap()之后使用peek()(它仅执行提供的表达式并返回具有与原始流相同的元素的新流)可以阐明元素的处理方式:
stream
.flatMap(l -> l.stream())
.peek(System.out::print)
.map(c -> (int)c)
.forEach(i -> System.out.format("%d ", i));
从输出中可以看到,flatMap()返回的流通过管道,就好像我们处理的是单个元素的流而不是元素列表的流:
a97 b98 c99 d100 e101 f102 g103
这样,使用flatMap()可以将Stream<List<Object>>转换为Stream<Object>。但是,重要的概念是此方法返回一个流,而不是单个元素(如map()那样)。
减少
归约是一种将多个元素组合起来并将它们归约成单个值或对象的操作。归约是通过多次应用操作来完成的。
一些约简的例子包括对N 个元素求和、找出N 个数字中的最大元素或者计数元素。
在下面的例子中,我们使用for循环将数字数组减少到它们的总和:
int[] numbers = {1, 2, 3, 4, 5, 6};
int sum = 0;
for(int n : numbers) {
sum += n;
}
当然,使用流而不是循环进行缩减有其好处,例如更容易并行化和提高可读性。
Stream 接口有两种减少方法:
collect()
reduce()
我们可以使用这两种方法来实现缩减,但是collect()帮助我们实现一种称为可变缩减的缩减类型,其中使用容器(如Collection)来累积操作的结果。
另一个缩减操作reduce()有三个版本:
Optional<T> reduce(BinaryOperator<T> accumulator)
T reduce(T identity,
BinaryOperator<T> accumulator)
<U> U reduce(U identity,
BiFunction<U,? super T,U> accumulator,
BinaryOperator<U> combiner)
请记住,BinaryOperator<T>相当于BiFunction<T, T, T>,其中两个参数和返回类型都是相同的类型。
让我们从接受一个参数的版本开始。这相当于:
boolean elementsFound = false;
T result = null;
for (T element : stream) {
if (!elementsFound) {
elementsFound = true;
result = element;
} else {
result = accumulator.apply(result, element);
}
return elementsFound ? Optional.of(result)
: Optional.empty();
此代码仅对每个元素应用一个函数,累积结果并返回包装该结果的Optional,如果没有元素,则返回一个空的Optional 。
让我们看一个具体的例子。我们只看到 sum 是如何变成 Reduce 操作的:
int[] numbers = {1, 2, 3, 4, 5, 6};
int sum = 0;
for(int n : numbers) {
sum += n;
}
这里,累加器的操作是:
sum += n; //or sum = sum + n
这意味着:
OptionalInt total = IntStream.of(1, 2, 3, 4, 5, 6)
.reduce( (sum, n) -> sum + n );
注意 Stream 的原始版本如何使用Optional的原始版本。
以下是具体发生的情况:
- 累积结果的内部变量被设置为流的第一个元素 (1)。
- 该累加器和流的第二个元素 (2) 作为参数传递给由 lambda 表达式(sum, n)
免责声明:本内容来源于第三方作者授权、网友推荐或互联网整理,旨在为广大用户提供学习与参考之用。所有文本和图片版权归原创网站或作者本人所有,其观点并不代表本站立场。如有任何版权侵犯或转载不当之情况,请与我们取得联系,我们将尽快进行相关处理与修改。感谢您的理解与支持!
请先 登录后发表评论 ~