电子商务微网站制作,创建企业手机微信网站门户,企业画册怎么设计,微信管理Java8实战-总结22 使用流数值流原始类型流特化数值范围数值流应用#xff1a;勾股数 使用流
数值流
可以使用reduce方法计算流中元素的总和。例如#xff0c;可以像下面这样计算菜单的热量#xff1a;
int calories menu.stream().map(Dish::getcalories).reduce(0, Int… Java8实战-总结22 使用流数值流原始类型流特化数值范围数值流应用勾股数 使用流
数值流
可以使用reduce方法计算流中元素的总和。例如可以像下面这样计算菜单的热量
int calories menu.stream().map(Dish::getcalories).reduce(0, Integer::sum);这段代码的问题是它有一个暗含的装箱成本。每个Integer都必须拆箱成一个原始类型再进行求和。要是可以直接像下面这样调用sum方法效果会更好
int calories menu.stream().map(Dish::getcalories).sum();但这是不可能的。问题在于map方法会生成一个StreamT。虽然流中的元素是Integer类型但Streams接口没有定义sum方法。Stream API提供了原始类型流特化专门支持处理数值流的方法。
原始类型流特化
Java 8引入了三个原始类型特化流接口来解决这个问题IntStream、DoubleStream和LongStream,分别将流中的元素特化为int、long和double,从而避免了暗含的装箱成本。每个接口都带来了进行常用数值归约的新方法比如对数值流求和的sum,找到最大元素的max。此外还有在必要时再把它们转换回对象流的方法。要记住的是这些特化的原因并不在于流的复杂性而是装箱造成的复杂性——即类似int和Integer之间的效率差异。
映射到数值流
将流转换为特化版本的常用方法是mapToInt、mapToDouble和mapToLong。这些方法和前面说的map方法的工作方式一样只是它们返回的是一个特化流而不是streamT。例如可以像下面这样用mapToInt对menu中的卡路里求和
int calories menu.stream().mapToInt(Dish::getcalories)//返回一个IntStream.sum();//返回一个StreamDish这里mapToInt会从每道菜中提取热量(用一个Integer表示),并返回一个IntStream(而不是一个StreamInteger)。然后就可以调用IntStream接口中定义的sum方法对卡路里求和了!请注意如果流是空的sum默认返回0。IntStream还支持其他的方便方法如max、min、average等。
转换回对象流
同样一旦有了数值流也可以把它转换回非特化流。例如IntStream上的操作只能产生原始整数IntStream的map操作接受的Lambda必须接受int并返回int(一个IntUnaryoperator)。但是可能想要生成另一类值比如Dish。为此需要访问stream接口中定义的那些更广义的操作。要把原始流转换成一般流(每个int都会装箱成一个Integer),可以使用boxed方法如下所示 将Stream转
IntStream intStream menu.stream().mapToInt(Dish::getcalories);//将Stream转换为数值流
StreamInteger stream intStream.boxed();//将数值流转换为Stream在需要将数值范围装箱成为一个一般流时boxed尤其有用。
默认值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之间的所有数字。Java 8引入了两个可以用于IntStream和LongStream的静态方法帮助生成这种范围range和rangeClosed。这两个方法都是第一个参数接受起始值第二个参数接受结束值。但range是不包含结束值的而rangeClosed则包含结束值。例子
//表示范围[1,100]
IntStream evenNumbers IntStream.rangeclosed(1, 100).filter(n - n % 2 0);//一个从1到100的偶数流System.out.println(evenNumbers.count());//从1到100有50个偶数这里用了rangeClosed方法来生成1到100之间的所有数字。它会产生一个流然后可以链接filter方法只选出偶数。到目前为止还没有进行任何计算。最后对生成的流调用count。因为count是一个终端操作所以它会处理流并返回结果50,这正是1到100(包括两端)中所有偶数的个数。请注意比较一下如果改用IntStream.range(1, 100),则结果将会是49个偶数因为range是不包含结束值的。
数值流应用勾股数
现在来看一个难一点儿的例子。
勾股数
某些三元数(a,b,c)满足公式a * a b * b c * c,其中a、b、c都是整数。例如(3, 4, 5)就是一组有效的勾股数因为3 * 3 4 * 4 5 * 5或9 16 25。这样的三元数有无限组。例如(5 , 12, 13)、(6, 8, 10)和(7, 24, 25)都是有效的勾股数。勾股数很有用因为它们描述的正好是直角三角形的三条边长如下图所示
表示三元数
第一步是定义一个三元数。虽然更恰当的做法是定义一个新的类来表示三元数但这里可以使用具有三个元素的int数组比如new int[]{3, 4, 5},来表示勾股数(3, 4, 5)。现在就可以用数组索引访问每个元素了。
筛选成立的组合
假定提供了三元数中的前两个数字a和b。怎么知道它是否能形成一组勾股数呢?需要测试a * a b * b的平方根是不是整数也就是说它没有小数部分——在Java里可以使用expr % 1表示。如果它不是整数那就是说c不是整数。可以用filter操作表达这个要求:
filter(b - Math.sqrt(a * a b * b) % 1 0)假设周围的代码给a提供了一个值并且stream提供了b可能出现的值filter将只选出那些可以与a组成勾股数的b。Math.sqrt(a * a b * b) %1 0这一行是一种测试Math.sqrt(a * a b * b)返回的结果是不是整数的方法。如果平方根的结果带了小数这个条件就不成立。
生成三元组
在筛选之后知道a和b能够组成一个正确的组合。现在需要创建一个三元组。可以使用map操作像下面这样把每个元素转换成一个勾股数组
stream.filter(b - Math.sqrt(a*a b*b) % 1 0).map(b - new int[]{a, b, (int)Math.sqrt(a*a b*b)});生成b值
现在需要生成b的值。前面已经看到Stream.rangeClosed可以在给定区间内生成一个数值流。可以用它来给b提供数值这里是1到100:
IntStream.rangeClosed(1, 100).filter(b - Math.sqrt(a*a b*b) % 1 0).boxed().map(b - new int[]{a, b, (int)Math.sqrt(a*a b*b)});在filter之后调用boxed,从rangeClosed返回的IntStream生成一个StreamInteger。这是因为map会为流中的每个元素返回一个int数组。而IntStream中的map方法只能为流中的每个元素返回另一个int,这不是想要的。可以用IntStream的mapToObj方法改写它这个方法会返回一个对象值流
IntStream.rangeClosed(1, 100).filter(b - Math.sqrt(a*a b*b) % 1 0).mapToobj(b - new int[]{a, b, (int)Math.sqrt(a*a b*b)});6 . 生成值
这里有一个关键的假设给出了a的值。 现在只要已知a的值就有了一个可以生成勾股数的流。就像b一样需要为a生成数值。最终的解决方案如下所示
Streamcint[] pythagoreanTriples IntStream.rangeClosed(1, 100).boxed().flatMap(a -IntStream.rangeClosed(a, 100).filter(b - Math.sqrt(a*a b*b) % 1 0).mapToobj(b -new int[]{a, b, (int)Math.sqrt(a * a b* b)})
);首先创建一个从1到100的数值范围来生成a的值。对每个给定的a值创建一个三元数流。要是把a的值映射到三元数流的话就会得到一个由流构成的流。flatMap方法在做映射的同时还会把所有生成的三元数流扁平化成一个流。这样就得到了一个三元数流。还要注意b的范围改成了a到100。没有必要再从1开始了否则就会造成重复的三元数例如(3,4,5)和(4,3,5)。
运行代码
现在可以运行解决方案并且可以利用前面的limit命令明确限定从生成的流中要返回多少组勾股数了
pythagoreanTriples.limit(5).forEach(t -System.out.println(t[0] , t[1] , t[2]));这会打印
3, 4, 5
5, 12, 13
6, 8, 10
7, 24, 25
8, 15, 17最优
目前的解决办法并不是最优的因为要求两次平方根。让代码更为紧凑的一种可能的方法是先生成所有的三元数(a*a, b*b, a*a b*b),然后再筛选符合条件的
Streamcdouble[] pythagoreanTriples2 IntStream.rangeClosed(1, 100).boxed().flatMap(a -IntStream.rangeClosed(a,100).mapToObj(b - new double[]{a, b, Math.sqrt(a*a b*b)})//产生三元数.filter(t - t[2] % 1 0));//元组中的第三个元素必须是整数