Java中Lambda表达式并行与组合行为
发布时间 - 2026-01-10 23:14:19 点击率:次从串行到并行

串行指一个步骤一个步骤地处理,也就是通常情况下,代码一行一行地执行。
如果将我们常用的迭代器式的循环展开的话,就是串行执行了循环体内所定义的操作:
sum += arr.get(0); sum += arr.get(1); sum += arr.get(2); //...
在书的一开始,就提到Java需要支持集合的并行计算(而Lambda为这个需求提供了可能)。
这些功能将全部被实现于库代码中,对于我们使用者,实现并行的复杂性被大大降低(最低程度上只需要调用相关方法)。
另外,关于并发与并行这两个概念,其实是不同的,如果不明白的话请自行了解,在此只引用一句非常流行的话:
一个是关于代码结构,一个是关于代码执行。
如果我们想将一个计算任务均匀地分配给CPU的四个内核,我们会给每个核分配一个用于计算的线程,每个线程上进行整个任务的子任务。
书上有一段非常形象的伪代码:
if the task list contains more than N/4 elements {
leftTask = task.getLeftHalf()
rightTask = task.getRightHalf()
doInparallel {
leftResult = leftTask.solve()
rightResult = rightTask.solve()
}
result = combine(leftResult, rightResult)
} else {
result = task.solveSequentially()
}
代码中,将每四个任务元素分为一组,用四个内核对其进行并行处理,然后每两组进行一次结果的合并,最终得到整个任务队列的最终结果。
从整体处理流程上看,先将任务队列递归地进行分组,并行处理每一组,然后将结果递归地进行合并(合并通过管道终止操作实现)。
Java8之前,开发者们使用一种针对集合的fork/join框架来实现该模式。
然而现在,想对代码进行性能优化,就是一件非常容易的事了。
还记得我们上一节中所得出的最终代码:
long validContactCounter = contactList.stream() .map(s -> new Contact().setName(s)) .filter(Contact::call) .count();
稍加改动:
long validContactCounter = contactList.parallelStream() .map(s -> new Contact().setName(s)) .filter(Contact::call) .count();
注意stream()变为parallelStream()
同时下图将展示如何根据四个核对上述任务进行分解处理,最终合并结果并终止管道。
注意递归分解的目的是使子任务们足够小来串行执行。
组合行为
Java写手应该知道,Java中并不存在纯粹的“函数”,只存在“方法”。也就是说,Java中的函数必须依赖于某一个类,或者作为类的某种行为存在。
而在其他语言中,存在纯函数,以CoffeeScript的语法,声明一个函数:
eat = (x) ->
alert("#{x} has been eatten!")
这种写法与Lambda表达式的语法非常相近,也就是说,相比于匿名内部类,Lambda表达式看上去更像是一种函数表达式。
对于函数,一个核心操作便是组合。如果要求一元二次函数的其中一个解sqrt(sqr(b) - 4 * a * c),便是对多个子函数进行了组合。
对于面向对象,我们通过解耦的方式来分解它,同样,我们也希望以此种方式分解一个函数行为。
首先,沿用上两节中使用的例子,对Contact类稍作修改,将name属性分拆为名和姓:
private String firstName; private String lastName;
假设我们现在想要对联系人们进行排序,创建自定义排序的Java标准方式是创建一个Comparator:
public interface Comparator<T> {
int compare(T o1, T o2);
//...
}
我们想通过比较名的首字母来为联系人排序:
Comparator<Contact> byFirstName = new Comparator<Contact>() {
@Override
public int compare(Contact o1, Contact o2) {
return Character.compare(o1.getFirstName().charAt(0), o2.getFirstName().charAt(0));
}
};
Lambda写法:
Comparator<Contact> byFirstNameLambdaForm = (o1, o2) -> Character.compare(o1.getFirstName().charAt(0), o2.getFirstName().charAt(0));
写完这段代码后,IDEA立即提醒我代码可以替换为Comparator.comparingInt(...),不过这是后话,暂且不表。
在上面的代码中,我们发现了组合行为,即Comparator<Contact>的compare(...)方法里面还套用了o.getFirstName()与Character.compare(...)这两个方法(为了简洁,这里暂不考虑charAt(...)),在java.util.function中,我们找到了这种函数的原型:
public interface Function<T, R> {
R apply(T t);
//...
}
接收一个T类型的参数,返回一个R类型的结果。
现在我们将“比较名的首字母”这个比较键的提取行为抽成一个函数对象的实例:
Function<Contact, Character> keyExtractor = o -> o.getFirstName().charAt(0);
再将“比较首字母”这个具体的比较行为抽出来:
Comparator<Character> keyComparator = (c1, c2) -> Character.compare(c1, c2);
有了keyExtractor和keyComparator,我们再来重新装配一下Comparator:
Comparator<Contact> byFirstNameAdvanced = (o1, o2) -> keyComparator.compare(keyExtractor.apply(o1), keyExtractor.apply(o2));
到了这一步,我们牺牲了简洁性,但获得了相应的灵活性,也就是说,如果我们改变比较键为姓而非名,只需改动keyExtractor为:
Function<Contact, Character> keyExtractor = o -> o.getLastName().charAt(0);
值得庆幸的是,库的设计者考虑到了这一自然比较的需求的普遍性,因此为Comparator接口提供了静态方法comparing(...),只需传入比较键的提取规则,就能针对该键生成相应的Comparator,是不是非常神奇:
Comparator<Contact> compareByFirstName = Comparator.comparing(keyExtractor);
即使我们想改变比较的规则,比如比较联系人姓与名的长度,也只需做些许改动:
Comparator<Contact> compareByNameLength = Comparator.comparing(p -> (p.getFirstName() + p.getLastName()).length());
这是一个重大的改进,它将我们所关注的焦点真正集中在了比较的规则上面,而不是大量地构建所必须的胶水代码。
comparing(...)通过接收一个简单的行为,进而基于这个行为构造出更加复杂的行为。
赞!
然而更赞的是,对于流和管道,我们所需要的改动甚至更少:
contacts.stream() .sorted(compareByNameLength) .forEach(c -> System.out.println(c.getFirstName() + " " + c.getLastName()));
小结
本章的代码:
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.function.Function;
public class Bar {
public static void main(String[] args) {
// long validContactCounter = contactList.parallelStream()
// .map(s -> new Contact().setFirstName(s))
// .filter(Contact::call)
// .count();
List<Contact> contacts = new ArrayList<Contact>() {{
add(new Contact().setFirstName("Foo").setLastName("Jack"));
add(new Contact().setFirstName("Bar").setLastName("Ma"));
add(new Contact().setFirstName("Olala").setLastName("Awesome"));
}};
Comparator<Contact> byFirstName = new Comparator<Contact>() {
@Override
public int compare(Contact o1, Contact o2) {
return Character.compare(o1.getFirstName().charAt(0), o2.getFirstName().charAt(0));
}
};
//--- Using Lambda form ---//
Comparator<Contact> byFirstNameLambdaForm = (o1, o2) ->
Character.compare(o1.getFirstName().charAt(0), o2.getFirstName().charAt(0));
Function<Contact, Character> keyExtractor = o -> o.getFirstName().charAt(0);
Comparator<Character> keyComparator = (c1, c2) ->
Character.compare(c1, c2);
Comparator<Contact> byFirstNameAdvanced = (o1, o2) ->
keyComparator.compare(keyExtractor.apply(o1), keyExtractor.apply(o2));
Comparator<Contact> compareByFirstName = Comparator.comparing(keyExtractor);
Comparator<Contact> compareByNameLength = Comparator.comparing(p -> (p.getFirstName() + p.getLastName()).length());
contacts.stream()
.sorted(compareByNameLength)
.forEach(c -> System.out.println(c.getFirstName() + " " + c.getLastName()));
}
}
以及运行结果:
Bar Ma Foo Jack Olala Awesome
以上所述是小编给大家介绍的Java中Lambda表达式并行与组合行为,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对网站的支持!
# java
# lambda表达式
# Java大文本并行计算实现过程解析
# Java8并行流中自定义线程池操作示例
# java利用CountDownLatch实现并行计算
# 浅谈在Java中使用Callable、Future进行并行编程
# 浅谈Java Fork/Join并行框架
# Java通过Fork/Join优化并行计算
# Java 并行数据处理和性能分析
# 递归
# 只需
# 的是
# 在此
# 首字母
# 这两个
# 也就是说
# 一个函数
# 小编
# 这是
# 这一
# 是一种
# 就能
# 一句
# 多个
# 而在
# 不明白
# 这段
# 这是一个
# 对其
相关栏目:
【
网站优化151355 】
【
网络推广146373 】
【
网络技术251813 】
【
AI营销90571 】
相关推荐:
Laravel如何优化应用性能?(缓存和优化命令)
佛山网站制作系统,佛山企业变更地址网上办理步骤?
手机怎么制作网站教程步骤,手机怎么做自己的网页链接?
详解jQuery中的事件
PHP 500报错的快速解决方法
网站制作免费,什么网站能看正片电影?
如何登录建站主机?访问步骤全解析
Laravel Blade模板引擎语法_Laravel Blade布局继承用法
如何在橙子建站上传落地页?操作指南详解
PHP的CURL方法curl_setopt()函数案例介绍(抓取网页,POST数据)
如何在阿里云购买域名并搭建网站?
如何在万网自助建站中设置域名及备案?
如何在香港服务器上快速搭建免备案网站?
北京专业网站制作设计师招聘,北京白云观官方网站?
如何在建站主机中优化服务器配置?
软银砸40亿美元收购DigitalBridge 强化AI资料中心布局
南京网站制作费用,南京远驱官方网站?
HTML5段落标签p和br怎么选_文本排版常用标签对比【解答】
高防服务器租用如何选择配置与防御等级?
利用vue写todolist单页应用
C++时间戳转换成日期时间的步骤和示例代码
海南网站制作公司有哪些,海口网是哪家的?
Laravel的辅助函数有哪些_Laravel常用Helpers函数提高开发效率
SQL查询语句优化的实用方法总结
Swift中循环语句中的转移语句 break 和 continue
香港网站服务器数量如何影响SEO优化效果?
网站视频制作书签怎么做,ie浏览器怎么将网站固定在书签工具栏?
Laravel Seeder怎么填充数据_Laravel数据库填充器的使用方法与技巧
如何在HTML表单中获取用户输入并用JavaScript动态控制复利计算循环
如何用免费手机建站系统零基础打造专业网站?
百度输入法ai组件怎么删除 百度输入法ai组件移除工具
标题:Vue + Vuex 项目中正确使用 JWT 进行身份认证的实践指南
深圳网站制作培训,深圳哪些招聘网站比较好?
制作电商网页,电商供应链怎么做?
如何制作新型网站程序文件,新型止水鱼鳞网要拆除吗?
桂林网站制作公司有哪些,桂林马拉松怎么报名?
如何快速重置建站主机并恢复默认配置?
悟空识字怎么关闭自动续费_悟空识字取消会员自动扣费步骤
Laravel如何使用Livewire构建动态组件?(入门代码)
如何彻底删除建站之星生成的Banner?
Laravel API资源类怎么用_Laravel API Resource数据转换
C#如何调用原生C++ COM对象详解
JavaScript如何实现路由_前端路由原理是什么
Laravel如何使用Telescope进行调试?(安装和使用教程)
Laravel如何创建自定义Artisan命令?(代码示例)
Laravel怎么写单元测试_PHPUnit在Laravel项目中的基础测试入门
Android自定义listview布局实现上拉加载下拉刷新功能
在Oracle关闭情况下如何修改spfile的参数
Laravel如何使用Contracts(契约)进行编程_Laravel契约接口与依赖反转
家族网站制作贴纸教程视频,用豆子做粘帖画怎么制作?

