初探Lambda表达式/Java多核编程【2】并行与组合行为
今天又翻了一下书的目录,第一章在这之后就结束了。也就是说,这本书所涉及到的新的知识已经全部点到了。
书的其余部分就是对这几个概念做一些基础知识的补充以及更深层次的实践。
最后两个小节的内容较少,所以合成一篇文章来总结。
上一篇:初探Lambda表达式/Java多核编程【1】从集合到流
从串行到并行
串行指一个步骤一个步骤地处理,也就是通常情况下,代码一行一行地执行。
如果将我们常用的迭代器式的循环展开的话,就是串行执行了循环体内所定义的操作:
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
初探Lambda表达式/Java多核编程【2】并行与组合行为的更多相关文章
- 初探Lambda表达式/Java多核编程【1】从集合到流
从集合到流 接上一小节初探Lambda表达式/Java多核编程[0]从外部迭代到内部迭代,本小节将着手使用"流"这一概念进行"迭代"操作. 首先何为" ...
- 初探Lambda表达式/Java多核编程【3】Lambda语法与作用域
接上一篇:初探Lambda表达式/Java多核编程[2]并行与组合行为 本节是第二章开篇,前一章已经浅显地将所有新概念点到,书中剩下的部分将对这些概念做一个基础知识的补充与深入探讨实践. 本章将介绍L ...
- 初探Lambda表达式/Java多核编程【4】Lambda变量捕获
这周开学,上了两天感觉课好多,学校现在还停水,宿舍网络也还没通,简直爆炸,感觉能静下心看书的时间越来越少了...寒假还有些看过书之后的存货,现在写一点发出来.加上假期两个月左右都过去了书才看了1/7都 ...
- 初探Lambda表达式/Java多核编程【0】从外部迭代到内部迭代
开篇 放假前从学校图书馆中借来一本书,Oracle官方的<精通Lambda表达式:Java多核编程>. 假期已过大半才想起来还没翻上几页,在此先推荐给大家. 此书内容及其简洁干练,如果你对 ...
- Lambda&Java多核编程-5-函数式接口与function包
从前面的总结中我们知道Lambda的使用场景是实现一个函数式接口,那么本篇就将阐述一下何为函数式接口以及Java的function包中提供的几种函数原型. 函数式接口 早期也叫作SAM(Single ...
- Lambda&Java多核编程-6-方法与构造器引用
在Lambda&Java多核编程-2-并行与组合行为一文中,我们对Stream<Contact>里的每一位联系人调用call()方法,并根据能否打通的返回结果过滤掉已经失效的项. ...
- Lambda表达式和函数式编程
Lambda表达式和函数式编程 https://www.cnblogs.com/bigbigbigo/p/8422579.html https://www.runoob.com/java/java8- ...
- Lambda&Java多核编程-7-类型检查
本篇主要介绍Lambda的类型检查机制以及周边的一些知识. 类型检查 在前面的实践中,我们发现表达式的类型能够被上下文所推断.即使同一个表达式,在不同的语境下也能够被推断成不同类型. 这几天在码一个安 ...
- Lambda 表达式(C# 编程指南) 微软microsoft官方说明
Visual Studio 2013 其他版本 Lambda 表达式是一种可用于创建委托或表达式目录树类型的匿名函数. 通过使用 lambda 表达式,可以写入可作为参数传递或作为函数调用值返回的本地 ...
随机推荐
- ACM_基础知识(二)
1. strstr: 函数原型:extern char *strstr(char *str1, const char *str2); 功能:strstr(str1,str2) 函数用于判断字符串str ...
- css 弹出框
最近想弄一个类似登陆框的那种弹出框,其实网上已经有很多例子,而且也有相应的插件,例如:jquery-ui的,可直接使用,而我就简单的弄了个简易版的登陆框,真的很简易. 其实原理就是设置两个div,一个 ...
- iOS开发实现Label中多颜色多字体
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(8, 100, 300, 30)]; label.textColor = wor ...
- IOS之富文本编辑 分类: ios技术 2015-03-06 22:51 89人阅读 评论(0) 收藏
之前做项目时遇到一个问题: 使用UITextView显示一段电影的简介,由于字数比较多,所以字体设置的很小,行间距和段间距也很小,一大段文字挤在一起看起来很别扭,想要把行间距调大,结 ...
- Maven项目中pom文件分析
pom英文全称: project object model 1.概述 pom.xml文件描述了maven项目的基本信息,比如groupId,artifactId,version等.也可以对maven项 ...
- cocoapod升级版本
原文 http://blog.csdn.net/sing_sing/article/details/49762359 该方法好用 sudo gem install -n /usr/local/bin ...
- IOS开发-UI学习-根据URL显示图片,下载图片的练习(button,textfield,image view,url,data)
编写一个如下界面,实现: 1.在文本输入框中输入一个网址,然后点击显示图片,图片显示到UIImageView中. 2.点击下载,这张显示的图片被下载到手机的Documents文件夹下的Dowmload ...
- 在Eclipse中配置tomcat
为了在Eclipse中进行struts2的测试,才发现自己机器上的Eclipse没有集成Tomcat,在网上找了半天,不是这个插件没有下载地址,就是那个有好多注意事项或者版本问题. 结果,自己到tom ...
- Node.js timer的优化故事
前几天nodejs发布了新版本4.0,其中涉及到一个更新比较多的模块,那就是下面要介绍的timer模块. timers: Improved timer performance from porting ...
- java系列--类和对象
一.成员属性,构造方法,成员方法 1.类名首字母一般大写 2.方法名的首字母一般是小写,使用驼峰法(匈牙利法) myCry, 下划线法 my_cry 3.方法的声明没有函数体(接口,抽象类),数据类型 ...