NVIDIA 初创加速计划,免费加速您的创业启动 了解详情
写点什么

浅析 Java 8 的聚合操作

  • 2014-06-26
  • 本文字数:2817 字

    阅读完需:约 9 分钟

Oracle 在 2014 年 3 月 19 日如期发布了 Java 8。Java 8 版本被认为是具有里程碑意义的一个版本,Oracle 在该版本中添加了许多新特性,包括 Lambda 表达式、方法引用、加强了安全等等。

在众多的新特性中,聚合操作(Aggregate Operations)是针对集合类的一个比较大的变化。通过聚合操作,开发者可以更容易地使用 Lambda 表达式,并且更方便地实现对集合的查找、遍历、过滤以及常见计算等。

聚合操作与 Java 8 中的 Lambda 表达式、方法引用等新特性是相关的,一般一起组合使用,但这里只说明聚合操作的使用,下面就聚合操作的使用进行简单说明。

集合类的层次结构

集合类是 Java 语言提供的辅助类,是一种较为通用的数据结构,如 Map、Set、List 等。Java 中集合类层次关系如下:

图 1

如上图,Collection 是主要集合类的接口,其子接口(具化接口)有 Deque、Queue、Set、List 等。

Map 是另一种类型的集合,以 Key、Value 的键值对存储数据集。

在 Java 8 中,在 java.util.Collection 接口中添加了如下方法:

复制代码
Stream<E> stream() {
return StreamSupport.stream(spliterator(), false);
}

stream() 方法的可见性修饰符为 default,这又是 Java 8 的新特性。在接口中(Collection 为 interface),本不需要(也不能)进行方法实现,但引入 default 修饰后就不同了。开发者不但可以进行方法的实现,而且还不用考虑向后兼容的问题。关于 Default Method 的详细解释,读者可以参考 Java 8 的官方文档。

正是 stream 方法引出了集合类的聚合操作。

[注意]

Map 接口中并没有 stream() 方法,但是 Map 的 values() 和 keySet() 均返回集合对象,在集合对象上当然是可以使用 stream() 方法的。

聚合操作实例

为说明聚合操作的使用,首先定义一个数据元素类 Person,如下:

复制代码
import java.time.LocalDate;
public class Person {
String name;
LocalDate birthday;
Sex gender;
String emailAddress;
public int getAge() {
return LocalDate.now().getYear() - birthday.getYear();
}
public void setBirthday(LocalDate birthday){
this.birthday = birthday;
}
public void setGender(Sex sex){
this.gender = sex;
}
public void printPerson() {
System.out.println("The name is " + name);
}
public Sex getGender(){
return gender;
}
public enum Sex {
MALE, FEMALE
}
}

在 Java 8 以前的版本中,对 Person 集合的遍历往往采用以下方式:

复制代码
Set<Person> persons = new HashSet<Person>();
<p>// 传统遍历方式 for (Person person : persons) { if (person.getAge() > 18) { System.out.println(person.name + " is elder than 18."); } }</p>

同样的功能,在 Java 8 中使用聚合操作,可以实现如下:

复制代码
// 使用聚合操作
persons.stream().filter(new Predicate<Person>() {
@Override
public boolean test(Person person) {
if (person.getAge() > 18) {
return true;
} else {
return false;
}
}
}).forEach(new Consumer<Person>() {
@Override
public void accept(Person person) {
System.out.println(person.name + " is elder than 18.");
}
});

首先,在集合对象 persons 上调用 stream() 方法(聚合操作),取得 person 对象的数据集(elements),然后调用聚合操作 filter() 对集合中的元素进行过滤,再调用 forEach() 完成对符合条件的 person 的打印。

Predicate 和 Consumer 为 Java 8 中定义的函数接口 (Functional Interface),在 java.util.function 包下面,函数接口也是 Java 8 的新特性。在上述代码中,使用了两个匿名类分别对 Predicate 和 Consumer 进行了实现,这两个接口都只有一个方法,这也是函数接口的特征之一。

上述代码中的写法还是比较繁琐的,为进一步简化,可以使用 Lambda 表达式实现,如下:

复制代码
// 使用聚合操作及 Lambda
persons.stream()
.filter(p -> p.getAge() >= 18)
.forEach(p -> System.out.println(p.name + " is elder than 18."));

因为 filter()、forEach() 的参数均为函数接口,所以可以替换为 Lambda 表达式的方式。简单来理解,Lambda 表达式就是允许开发者将代码逻辑作为参数进行传递,关于 Lambda 表达式的详细内容,请参 Java 8 的官方文档。

聚合操作的使用

聚合操作是 Java 8 针对集合类,使编程更为便利的方式,可以与 Lambda 表达式一起使用,达到更加简洁的目的。

前面例子中,对聚合操作的使用可以归结为 3 个部分:

  1. 数据源部分:通过 stream() 方法,取得集合对象的数据集。
  2. 通过一系列中间(Intermediate)方法,对数据集进行过滤、检索等数据集的再次处理。如上例中,使用 filter() 方法来对数据集进行过滤。
  3. 通过最终(terminal)方法完成对数据集中元素的处理。如上例中,使用 forEach() 完成对过滤后元素的打印。

中间方法除了 filter() 外,还有 distinct()、sorted()、map() 等等,其一般是对数据集的整理(过滤、排序、匹配、抽取等等),返回值一般也是数据集。

最终方法往往是完成对数据集中数据的处理,如 forEach(),还有 allMatch()、anyMatch()、findAny()、findFirst(),数值计算类的方法有 sum、max、min、average 等等。最终方法也可以是对集合的处理,如 reduce()、collect() 等等。reduce() 方法的处理方式一般是每次都产生新的数据集,而 collect() 方法是在原数据集的基础上进行更新,过程中不产生新的数据集。

从上面的例子中可以看出,通过 stream() 方法,从集合对象获取的数据集与集合对象的迭代器(Iterator)有些类似,但他们也不完全相同:

  1. 迭代器提供 next()、hasNext() 等方法,开发者可以自行控制对元素的处理,以及处理方式,但是只能顺序处理;
  2. stream() 方法返回的数据集无 next() 等方法,开发者无法控制对元素的迭代,迭代方式是系统内部实现的,同时系统内的迭代也不一定是顺序的,还可以并行,如 parallelStream() 方法。并行的方式在一些情况下,可以大幅提升处理的效率。

除上述介绍的聚合操作外,Java 8 中还提供了其他更为丰富的聚合操作,读者可以参考 Java 8 的开发参考,了解更多内容。

总结

Java 8 提供的聚合操作,以及一起使用的 Lambda 表达式为开发者带来了便利,尤其在面向逻辑易变、开发迭代较快的项目应用时。但笔者个人认为,在带来方便的同时,可能也带来了一些麻烦,如相同逻辑的复用,以及代码的查错、修改等,当然这些问题也是相对而言的。毕竟,任何事物都有两面性,技术在不断的发展,Java 也在不断地调整自己的适应性,变得功能越来越多,越来越强大了。


感谢张龙对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ )或者腾讯微博( @InfoQ )关注我们,并与我们的编辑和其他读者朋友交流。

2014-06-26 01:207537

评论

发布
暂无评论
发现更多内容

面试Go语言开发?让这本书帮你感动面试官!

博文视点Broadview

【浪潮云说】直播间第七期今日准时开播!

浪潮云

云计算运维

接口测试,负载测试,并发测试,压力测试区别

与风逐梦

软件测试 接口测试

Golang并发操作中常见的死锁情形

Regan Yue

协程 Go 语言 8月日更

springboot+cloud实战派PDF让开发和微服务架构像喝水一样简单

Java~~~

Java spring 架构 面试 Spring Boot

适女化科技(二):让女性更安全的两条技术路径:软件硬件化与硬件软件化

脑极体

解析ThreadPoolExecutor类是如何保证线程池正确运行的

华为云开发者联盟

线程池 任务 注释 Worker类

Hadoop MapReduce原理、序列化

Mike

🏆【Alibaba微服务技术系列】「Dubbo3.0技术专题」回顾Dubbo2.x的技术原理和功能实现及源码分析(温故而知新)

洛神灬殇

dubbo RPC 8月日更 Dubbo3

阿里巴巴新产“Java架构核心宝典”,全是流行技术,限时开放

Java 架构 面试 后端 计算机

三面阿里被挂,竟获内推名额,历经5面拿下口碑offer(Java后台)

Java 程序员 架构 面试 计算机

最近很火的低代码到底是什么?

禅道项目管理

大前端 测试开发 语言 & 开发

易华录 X ShardingSphere|葫芦 App 后台数据处理的逻辑捷径

SphereEx

数据库 开源

突破四大要素  飞算SoFlu助力企业实现DevOps落地

SoFlu软件机器人

DevOps 自动化 软件工程

百度信誉认证中台架构解析

百度Geek说

后端 软件架构 中台架构

最近很火的低代码到底是什么?

禅道项目管理

低代码 可视化 低代码平台

故事点数VS工时,研发工作量到底怎么算?

LigaAI

敏捷开发 故事点数 工时 研发工作量

数仓出现“wait in ccn queue”的时候,怎么迅速定位处理?

华为云开发者联盟

线程 hash 负载 数仓 GaussDB(DWS)

一起吐槽接口文档

FunTester

接口文档 接口测试 API Jira FunTester

自主创新国产化科技:智能制造之 SMT 产线监控管理可视化

一只数据鲸鱼

数据可视化 工业4.0 制造业 智慧工厂

带你走进MySQL全新高可用解决方案-MGR

vivo互联网技术

数据库 分布式 MySQL 高可用

开源demo| 视频应用类开源 Demo 大盘点

anyRTC开发者

音视频 视频直播 直播连麦 视频通话

CompletableFuture真香,可以替代CountDownLatch!

架构 面试 后端 计算机

互动直播应用快速开发实践(基于声网)

大伟

信息爆炸!78天闭门深造千页SpringCloud,再战京东

Java~~~

Java spring 架构 面试 Spring Cloud

叹服!阿里自述SpringCloud微服务:入门+实战+案例

Java~~~

Java spring 架构 面试 Spring Cloud

云小课|原来云备份不仅仅是能备份...

华为云开发者联盟

云备份 迁移数据 复制备份

国产接口工具ApiPost如何利用CryptoJS对请求参数进行MD5/AES加解密

Proud lion

大前端 后端 加密解密 Postman 接口文档

训练千亿参数模型的法宝,昇腾CANN异构计算架构来了~

华为云开发者联盟

盘古 CANN 千亿参数模型 异构计算 计算架构

图灵奖得主大全 深度学习经典论文翻译合集 John 易筋 ARTS 打卡 Week 62

John(易筋)

ARTS 打卡计划

一个内核漏洞详解:容器逃逸

程序员 架构 面试 计算机

浅析Java 8的聚合操作_Java_赵永_InfoQ精选文章