例如,像下面这样计算菜单的热量:
int calories = menu .stream()
. map (Dish: :getCalories )
. reduce(0,Integer: :sum) ;
这段代码的问题是,它有一个暗含的装箱成本。
每个Integer都必须拆箱成一个原始类型,再进行求和。
要是可以直接像下面这样调用sum方法,岂不是更好?
int calories = menu .stream()
. map (Dish: :getCalories)
. sum() ;
但这是不可能的,问题在于map方法会生成一个Stream
虽然流中的元素是Integer类型,但Streams接口没有定义sum方法。
但Stream API还提供了原始类型流特化,专门支持处理数值流的方法。

原始类型流特化

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

1、映射到数值流

将流转换为特化版本的常用方法是mapToInt、mapToDouble和mapToLong。
这些方法和前面说的map方法的工作方式一样,只是它们返回的是一个特化流,而不是Stream
例如,你可以像下面这样用mapToInt对menu中的卡路里求和:
image.png
这里,mapToInt会从每道菜中提取热量( 用一个Integer表示),并返回一个IntStream(而不是一个st ream )。
然后就可以调用IntStream接口中定义的sum方法,对卡路里求和了。
请注意,如果流是空的,sum默认返回0。
Intstream还支持其他的方便方法,如max、min、average等。

2、转换回对象流

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

3、默认值optionalInt

求和的那个例子很容易,因为它有一个默认值: 0。
但是,如果你要计算IntStream中的最大元素,就得换个法子了,因为0是错误的结果。
如何区分没有元素的流和最大值真的是0的流呢?
前面我们介绍了optional类,这是一个可以表示值存在或不存在的容器。
Optional可以用Integer、string等参考类型来参数化。
对于三种原始流特化,也分别有一个optional原始类型特化版本: OptionalInt、OptionalDouble和optionalLong。
例如,要找到IntStream中的最大元素,可以调用max方法,它会返回一个OptionalInt:
image.png
如果没有最大值的话,就可以显式处理OptionalInt去定义一个默认值了:
image.png

4、数值范围

比如,假设想要生成1和100之间的所有数字。
Java8引入了两个可以用于IntStream和LongStream的静态方法,帮助生成这种范围: range和rangeClosed。
这两个方法都是第一个参数接受起始值,第二个参数接受结束值。
但range是不包含结束值的,而rangeClosea则包含结束值。
来看一个例子:
image.png
这里用了rangeCl osed方法来生成1到100之间的所有数字。
它会产生一个流,然后你可以链接filter方法,只选出偶数。
最后,你对生成的流调用count。
因为count是一个终端操作,所以它会处理流,并返回结果50,这正是1到100 (包括两端)中所有偶数的个数。
请注意,比较一下,如果改用IntStream .range(1, 100), 则结果将会是49个偶数,因为range是不包含结束值的。