Java 8新特性之旅:使用Stream API处理集合
作者:网络转载 发布时间:[ 2015/8/28 10:44:35 ] 推荐标签:测试开发技术 编程语言
public static void main(String args[]) {
System.out.println("Creating list");
List<string> strings = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
strings.add("Item " + i);
}
strings.stream()
.forEach(str -> System.out.println(str));
}
运行这段代码后,获得了一个我所预期的结果。在屏幕上输出的顺序与添加到列表中的顺序是一致的。
.........
Item 9982
Item 9983
Item 9984
Item 9985
Item 9986
Item 9987
Item 9988
Item 9989
Item 9990
Item 9991
Item 9992
Item 9993
Item 9994
Item 9995
Item 9996
Item 9997
Item 9998
Item 9999
现在,让我们看一下当转换成并行流后会发生什么。正如我之前所描述的,我即可以调用parallelStream方法,也可以在流上调用parallel方法。
我将采用第二种方法。现在,我可以使用并行流了,该流可以根据负载分配到多个处理器来处理。
strings.stream()
.parallel()
.forEach(str -> System.out.println(str));
再次运行该段代码,然后观察会发生什么。注意,现在后打印的元素不是列表中后一个元素,后一个元素应该是9999。如果我滚动输出结果,能发现处理过程以某种方式在循环跳动。这是因为在运行时将数据划分成了多个块。
.........
Item 5292
Item 5293
Item 5294
Item 5295
Item 5296
Item 5297
Item 5298
Item 5299
Item 5300
Item 5301
Item 5302
Item 5303
Item 5304
Item 5305
Item 5306
Item 5307
Item 5308
Item 5309
Item 5310
Item 5311
然后,将数据块分配给合适的处理器去处理。只有当所有块都处理完成了,才会执行之后的代码。本质上讲,这是在调用 forEach() 方法时,将整个过程是根据需要来进行划分了。现在,这么做可能会提高性能,也可能不会。这依赖于数据集的大小以及你硬件的性能。通过这个例子,也可以看 出,如果需要按照添加的顺序挨个处理每一项,那么并行流可能不合适了。
串行流能保证每次运行的顺序是一致的。但并行流,从定义上讲,是一种更有效率的方式。所以并行流在聚合操作的时候非常有效。很适合将集合作为一个整体考虑,然后在该集合上进行一些聚合操作的情况。我将会通过一个例子来演示集合元素的计数、求平均值及求和操作。
我们在这个类的 main 方法中来计数,开始还是用相同的基础代码。创建10,000个字符串的列表。然后通过一个 for each 方法循环处理每一项。
public static void main(String args[]) {
System.out.println("Creating list");
List<string> strings = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
strings.add("Item " + i);
}
strings.stream()
.forEach(str -> System.out.println(str));
}
在这个例子中,我想直接对集合元素进行计数,而不是挨个来处理。所以,我注释掉原来的代码,使用下面的代码。因为不能准确的知道该集合到底有多少个元素。所以我使用长整型变量来存储结果。
我将这个变量命名为count,通过调用集合strings的.stream(), .count()方法,返回一个长整型的值。然后将这个值与“count:”拼接起来,再通过system的output来打印。
// strings.stream()
// .forEach(str -> System.out.println(str));
long count = strings.stream().count();
System.out.println("Count: " + count);
保存并运行该段代码,下面是输出结果。集合中元素数量的统计几乎是瞬间完成。
Creating list
Count: 10000
现在对上面的代码做一点小小的改动,增加两个0。现在,开始处理1000,000个字符串。我再次运行这段代码,也很快返回结果了。
Creating list
Count: 1000000
现在,我使用并行流来处理,看会发生什么。我在下面增加 parallel 方法:
// strings.stream()
// .forEach(str -> System.out.println(str));
long count = strings.stream().parallel().count();
System.out.println("Count: " + count);
然后我运行这段代码,发现花费的时间更长一点了。现在,我做一个基准测试,通过抓取操作前后的时间戳来观察发生了什么。然后做一点数学的事情。不同的系统 上,得到的结果可能不同。但是根据我的经验来说,这种包含简单类型的简单集合,使用并行流并没有太多的优势。不过,我还是鼓励你去自己做基准测试,虽然有 点麻烦。 不过这也要你是如何去做的。
再让我们看一下求和及求均值。我将使用 SumAndAverage 类。这次,我有一个包含三个 person 对象的列表,每个 person 对象的有不同的年龄值。我的目的是求三个年龄的和及年龄的平均值。我在所有的 person 对象都加入到列表之后加入了一行新的代码。然后,我创建了一个名为sum的整型变量。
首先,我通过 pepole.stream() 方法获取一个流。在这个流基础上,我可以调用 mapToInt() 方法。注意,还有两个类似的 Map Method:mapToDouble() 和 mapToLong()。这些方法的目的是,从复合类型中获取简单的基本类型数据,创建流对象。你可以用 lambda 表达式来完成这项工作。所以,我选择 mapToInt() 方法,因为每个人的年龄都是整数。
关于 Lambda 表达式,开始是一个代表当前 person 的变量。然后,通过 Lambda 操作符和 Lambda 表达式(p.getAge())返回一个整数。这种返回值,我们有时也叫做int字符串。也可以返回double字符串或其它类型。现在,由于已经知道它 是一个数字类型的值,所以我可以调用 sum() 方法。现在,我已经将所有集合中 person 对象的年龄值全部加起来了。通过一条语句,我可以用 System Output 来输出结果了。我将求和的结果与“Total of ages”连接在一起输出。
List<person> people = new ArrayList<>();
people.add(new Person("Mohamed", 69));
people.add(new Person("Doaa", 25));
people.add(new Person("Malik", 6));
int sum = people.stream()
.mapToInt(p -> p.getAge())
.sum();
System.out.println("Total of ages " + sum);
保存并运行上面的代码。三个年龄的总和是100。
Total of ages 100
求这些值的平均值非常类似。但是,求平均值需要做除法操作,所以需要考虑除数为0的问题,因此,当你求平均值的时候,可以返回一个Optional的变量。
你可以使用多种数据类型。在计算平均值的时候,我想获得一个 doule 类型的值。所以,我创建了一个 OptionalDouble 类型的变量。注意,还存在 Optional Int 和 Optional Long。我将平均值命名为 avg,使用的代码与求和的代码也是一致的,开始用 people.stream()。在这个基础上,再次使用 mapToInt()。并且传递了相同的 lambda 表达式,后,调用 average 方法。
现在,获得了一个OptionalDouble类型的变量。在处理这个变量前,你可以通过 isPresent() 来确保它确实是一个double值。所以,我使用了一段 if/else 的模板代码来处理。判定的条件是 avg.isPresent()。如果条件为真,使用 System Output 输出“Average”标签和平均值。在 else 子句中,我简单地打印“average wasn’t calculated”。
OptionalDouble avg = people.stream()
.mapToInt(p -> p.getAge())
.average();
if (avg.isPresent()) {
System.out.println("Average: " + avg);
} else {
System.out.println("average wasn't calculated");
}
现在,在这个例子中,我知道能成功,因为我给三个人的年龄都赋值了。但是,情况不总是这样的。正如我前面说的,存在除0的情况,这时你不能获取到一个 double 类型返回值。我保存并运行这段代码,请注意 optional double 类,它是一个复合对象。
Total of ages 100
Average: OptionalDouble[33.333333333333336]
所以,真实的值被包含在该类型中,回到这段代码,直接引用该对象,并调用 getAsDouble() 方法。
if (avg.isPresent()) {
System.out.println("Average: " + avg.getAsDouble());
} else {
System.out.println("average wasn't calculated");
}
现在,我可以获得 double 类型的值。我再次运行这段代码,输出结果如下:
Total of ages 100
Average: 33.333333333333336
结论
通过流和 lambda 表达式,你可以用非常非常少的代码可以完成集合的聚合计算。
本文内容不用于商业目的,如涉及知识产权问题,请权利人联系SPASVO小编(021-61079698-8054),我们将立即处理,马上删除。
相关推荐
Java性能测试有哪些不为众人所知的原则?Java设计模式??装饰者模式谈谈Java中遍历Map的几种方法Java Web入门必知你需要理解的Java反射机制知识总结编写更好的Java单元测试的7个技巧编程常用的几种时间戳转换(java .net 数据库)适合Java开发者学习的Python入门教程Java webdriver如何获取浏览器新窗口中的元素?Java重写与重载(区别与用途)Java变量的分类与初始化JavaScript有这几种测试分类Java有哪四个核心技术?给 Java开发者的10个大数据工具和框架Java中几个常用设计模式汇总java生态圈常用技术框架、开源中间件,系统架构及经典案例等
更新发布
功能测试和接口测试的区别
2023/3/23 14:23:39如何写好测试用例文档
2023/3/22 16:17:39常用的选择回归测试的方式有哪些?
2022/6/14 16:14:27测试流程中需要重点把关几个过程?
2021/10/18 15:37:44性能测试的七种方法
2021/9/17 15:19:29全链路压测优化思路
2021/9/14 15:42:25性能测试流程浅谈
2021/5/28 17:25:47常见的APP性能测试指标
2021/5/8 17:01:11热门文章
常见的移动App Bug??崩溃的测试用例设计如何用Jmeter做压力测试QC使用说明APP压力测试入门教程移动app测试中的主要问题jenkins+testng+ant+webdriver持续集成测试使用JMeter进行HTTP负载测试Selenium 2.0 WebDriver 使用指南