Java 8 数据流

       我们在前面看到了可以使用reduce方法计算流中元素的总和。例如,你可以想下面这样计算菜单的热量:

int calories = menu.stream().map(Dish::getCalories).reduce(0, Integer::sum);

       这段代码的问题是,他有一个暗含的装箱成本。每个Integer都必须拆箱成一个原始类型,再进行求和。要是可以直接像下面这样调用sum方法,岂不是更好?

int calories = menu.stream().map(Dish::getCalories).sum();

       但这是不可能的。问题在于map方法会生成一个Stream<T>。虽然流中的元素是Integer类型,但stream接口没有定义sum方法。为什么没有呢?比方说,你只有一个像menu那样的Stream<Dish>,把各种菜加起来是没有任何意义的。但不要担心,Stream Api还提供了原始类型流特化,专门支持处理数据流的方法。

原始类型流特化

       Java8引入了三个原始类型特化流接口来解决这个问题:IntStream(),DoubleStream()和LongStream(),分别将流中元素特化为int,long,double,从而避免了暗含的封箱成本。每个接口都带来了进行常用数值归约,比如对于数值流求和sum,找到最大元素max。此外还有在必要时在把它们转换成对象流的方法。要记住的是,这些特化的原因并不在于流的复杂化,而是装箱造成的复杂性——即类似int和Integer之间的效率差异。

  1. 映射到数值流

       将流转换为特化版本的常用方法是mapToInt,mapToDouble和mapToLong。这些方法和前面说的map方法的工作方式一样,只是他们返回的是一个特化流,而不是Stream<T>。例如,你可以像下面用mapToInt对menu的卡路里求和:

int calories = menu.stream().mapToInt(Dish::getCalories).sum();

       这里,mapToInt会从每道菜中提取热量(用一个Integer表示),并返回一个IntStream(而不是一个Stream<Integer>)。然后你就可以调用IntStream()接口中定义sum方法,对卡路里求和了!请注意,如果流是空的,sum默认返回0.IntStream()还支持其他的方便方法,如max,min,average等。

  1. 转换回对象流

        同样,一旦有了数据流,你可能会想把它转换回非特化流。例如,IntStream上的操作只能产生原始整数:IntStream的map操作接受的Lambda必须接收int并返回int(一个IntUnaryOperator).但是你可能想要生成另一类值,比如Dish。为此,你需要访问Stream()接口中定义的更加广义的操作。要把原始流转换成一般流(每个int都会装箱成一个Integer),可以使用boxed方法,如下所示:

IntStream intStream = menu.stream().mapToInt(Dish::getCalories);

Stream<Integer> stream = intStream.boxed();

下一个节中会看到,在需要将数值范围装箱成一个一般流时,boxed尤其重要。

  1. 默认值OptionalInt

        求和的那个例子很容易,因为它有一个默认值;0。但是,如果你要计算IntStream中的最大元素,就得换个法子,因为0是是一个错误的结果。如何区分没有元素的流和最大值真的是0的流呢?前面我们极少了Optional类,这是一个可以表示值存在或不存在的容器。Optional可以用Integer,String等参考类型来参数化。对于三种原始流特化,也分别有一个Optional原始类型特化版本:OptionalInt,OptionalDouble,OptionalLong。

例如,要找到IntStream中的最大元素,可以调用max方法,它会返回一个OptionalInt:

OptionalInt maxCalories = menu.stream().mapToInt(Dish::getCalories).max();

现在,如果没有最大值的话,你就可以显示处理OptionalInt去定义一个默认值了:

int max = maxCalories.orElse(1);

数值范围

       和数字打交道时,有一个常用的东西就是数值范围。比如,假设你想要生成1和100之间的所有数字。Java8 引入了两个可以用于IntStream和LongStream的静态方法,帮助生成这种范围:range和rangeColsed。这两个方法都是第一个参数接受起始值,第二个参数接收结束值。但range时不包含结束值的,而rangeClosed则包含结束值。让我们来看一个例子:

IntStream evenNumbers = IntStream.rangeClosed(1, 100).filter(n -> n % 2 == 0);

一个从1到100的偶数流。

System.out.println(evenNumbers.count())

        这里我们用了rangeClosed方法来生成1到100之间的所有数字。它会产生一个流,然后你可以链接filter方法,只选出偶数。到目前为止还没有进行任何计算。最后,你对生成的流调用count。因为count是一个终端操作。所以它会处理流,并返回结果50,这正是1到100(包括2端)中所有偶数个数。请注意,比较一下,如果改用IntStream.range(1,100),则结果将会是49个偶数,因为range是不包含结束值。

参考文献:java8 实战

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值