• Java8实战-总结22


    使用流

    数值流

    可以使用reduce方法计算流中元素的总和。例如,可以像下面这样计算菜单的热量:

    int calories = menu.stream()
    			.map(Dish::getcalories)
    			.reduce(0, Integer::sum);
    
    • 1
    • 2
    • 3

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

    int calories = menu.stream()
    					.map(Dish::getcalories)
    					.sum();
    
    • 1
    • 2
    • 3

    但这是不可能的。问题在于map方法会生成一个Stream。虽然流中的元素是Integer类型,但Streams接口没有定义sum方法。Stream API提供了原始类型流特化,专门支持处理数值流的方法。

    原始类型流特化

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

    1. 映射到数值流

    将流转换为特化版本的常用方法是mapToIntmapToDoublemapToLong。这些方法和前面说的map方法的工作方式一样,只是它们返回的是一个特化流,而不是stream。例如,可以像下面这样用mapToIntmenu中的卡路里求和:

    int calories = menu.stream()
    					.mapToInt(Dish::getcalories)//返回一个IntStream
    					.sum();//返回一个Stream
    
    • 1
    • 2
    • 3

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

    1. 转换回对象流

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

    IntStream intStream = menu.stream().mapToInt(Dish::getcalories);//将Stream转换为数值流
    Stream<Integer> stream = intStream.boxed();//将数值流转换为Stream
    
    • 1
    • 2

    在需要将数值范围装箱成为一个一般流时,boxed尤其有用。

    1. 默认值optionalInt

    求和的例子很容易,因为它有一个默认值:0。但是,如果要计算IntStream中的最大元素,就得换个法子了,因为0是错误的结果。如何区分没有元素的流和最大值真的是0的流呢?前面介绍了optional类,这是一个可以表示值存在或不存在的容器。Optional可以用IntegerString等参考类型来参数化。对于三种原始流特化,也分别有一个optional原始类型特化版本:optionalIntoptionalDoubleoptionalLong
    例如,要找到IntStream中的最大元素,可以调用max方法,它会返回一个optionalInt:

    OptionalInt maxCalories = menu.stream()
    					.mapToInt(Dish::getcalories)
    					.max();
    
    • 1
    • 2
    • 3

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

    int max = maxCalories.orElse(1);如果没有最大值的话,显式提供一个默认最大值
    
    • 1

    数值范围

    和数字打交道时,有一个常用的东西就是数值范围。比如,假设想要生成1和100之间的所有数字。Java 8引入了两个可以用于IntStreamLongStream的静态方法,帮助生成这种范围:rangerangeClosed。这两个方法都是第一个参数接受起始值,第二个参数接受结束值。但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个偶数
    
    • 1
    • 2
    • 3
    • 4
    • 5

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

    数值流应用:勾股数

    现在来看一个难一点儿的例子。

    1. 勾股数

    某些三元数(a,b,c)满足公式a * a + b * b = c * c,其中a、b、c都是整数。例如,(3, 4, 5)就是一组有效的勾股数,因为3 * 3 + 4 * 4 = 5 * 59 + 16 = 25。这样的三元数有无限组。例如,(5 , 12, 13)(6, 8, 10)(7, 24, 25)都是有效的勾股数。勾股数很有用,因为它们描述的正好是直角三角形的三条边长,如下图所示:
    在这里插入图片描述

    1. 表示三元数

    第一步是定义一个三元数。虽然更恰当的做法是定义一个新的类来表示三元数,但这里可以使用具有三个元素的int数组,比如new int[]{3, 4, 5},来表示勾股数(3, 4, 5)。现在就可以用数组索引访问每个元素了。

    1. 筛选成立的组合

    假定提供了三元数中的前两个数字:ab。怎么知道它是否能形成一组勾股数呢?需要测试a * a + b * b的平方根是不是整数,也就是说它没有小数部分——在Java里可以使用expr % 1表示。如果它不是整数,那就是说c不是整数。可以用filter操作表达这个要求:

    filter(b -> Math.sqrt(a * a + b * b) % 1 == 0)
    
    • 1

    假设周围的代码给a提供了一个值,并且stream提供了b可能出现的值,filter将只选出那些可以与a组成勾股数的bMath.sqrt(a * a + b * b) %1 == 0这一行是一种测试Math.sqrt(a * a + b * b)返回的结果是不是整数的方法。如果平方根的结果带了小数,这个条件就不成立。

    1. 生成三元组

    在筛选之后,知道ab能够组成一个正确的组合。现在需要创建一个三元组。可以使用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)});
    
    • 1
    • 2
    1. 生成b值

    现在需要生成b的值。前面已经看到,Stream.rangeClosed可以在给定区间内生成一个数值流。可以用它来给b提供数值,这里是1100:

    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)});
    
    • 1
    • 2
    • 3
    • 4

    filter之后调用boxed,从rangeClosed返回的IntStream生成一个Stream。这是因为map会为流中的每个元素返回一个int数组。而IntStream中的map方法只能为流中的每个元素返回另一个int,这不是想要的。可以用IntStreammapToObj方法改写它,这个方法会返回一个对象值流:

    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)});
    
    • 1
    • 2
    • 3

    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    首先,创建一个从1100的数值范围来生成a的值。对每个给定的a值,创建一个三元数流。要是把a的值映射到三元数流的话,就会得到一个由流构成的流。flatMap方法在做映射的同时,还会把所有生成的三元数流扁平化成一个流。这样就得到了一个三元数流。还要注意,b的范围改成了a100。没有必要再从1开始了,否则就会造成重复的三元数,例如(3,4,5)(4,3,5)

    1. 运行代码

    现在可以运行解决方案,并且可以利用前面的limit命令,明确限定从生成的流中要返回多少组勾股数了:

    pythagoreanTriples.limit(5)
    		.forEach(t ->
    				System.out.println(t[0] + "," + t[1] + "," + t[2]));
    
    • 1
    • 2
    • 3

    这会打印:

    3, 4, 5
    5, 12, 13
    6, 8, 10
    7, 24, 25
    8, 15, 17
    
    • 1
    • 2
    • 3
    • 4
    • 5
    1. 最优

    目前的解决办法并不是最优的,因为要求两次平方根。让代码更为紧凑的一种可能的方法是,先生成所有的三元数(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));//元组中的第三个元素必须是整数
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
  • 相关阅读:
    如何用java校验SQL语句的合法性?(提供五种解决方案)
    Vue 拖拽功能 之 自定义指令实现元素拖拽功能
    磷脂-聚乙二醇-酰肼,DSPE-PEG-Hydrazide,DSPE-PEG-HZ,MW:5000
    构建高可用性的 SQL Server:Docker 容器下的主从同步实现
    Mac -bash: ls: command not found和无法运行.sh脚本
    【网络篇】第十八篇——ping的工作原理
    Vue中的路由介绍以及Node.js的使用
    糖友吃什么有助于控制血糖
    java毕业设计软件基于ssh+mysql+jsp的电影|影院购票选座系统
    【TensorFlow深度学习】创建与操作张量的典型实践与技巧
  • 原文地址:https://blog.csdn.net/weixin_42583701/article/details/132725003