Java中Lambda表达式并行与组合行为

网友投稿 502 2023-06-08


Java中Lambda表达式并行与组合行为

从串行到并行

串行指一个步骤一个步骤地处理,也就是通常情况下,代码一行一行地执行。

如果将我们常用的迭代器式的循环展开的话,就是串行执行了循环体内所定义的操作:

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中的函数必须依赖于某一个类,或者作为类的某种行为存在。

而在其他语言中,存在纯函数,以Co

ffeeScript的语法,声明一个函数:

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 {

int compare(T o1, T o2);

//...

}

我们想通过比较名的首字母来为联系人排序:

Comparator byFirstName = new Comparator() {

@Override

public int compare(Contact o1, Contact o2) {

return Character.compare(o1.getFirstName().charAt(0), o2.getFirstName().charAt(0));

}

};

Lambda写法:

Comparator byFirstNameLambdaForm = (o1, o2) ->

Character.compare(o1.getFirstName().charAt(0), o2.getFirstNameTCmfG().charAt(0));

写完这段代码后,IDEA立即提醒我代码可以替换为Comparator.comparingInt(...),不过这是后话,暂且不表。

在上面的代码中,我们发现了组合行为,即Comparator的compare(...)方法里面还套用了o.getFirstName()与Character.compare(...)这两个方法(为了简洁,这里暂不考虑charAt(...)),在java.util.function中,我们找到了这种函数的原型:

public interface Function {

R apply(T t);

//...

}

接收一个T类型的参数,返回一个R类型的结果。

现在我们将“比较名的首字母”这个比较键的提取行为抽成一个函数对象的实例:

Function keyExtractor = o -> o.getFirstName().charAt(0);

再将“比较首字母”这个具体的比较行为抽出来:

Comparator keyComparator = (c1, c2) -> Character.compare(c1, c2);

有了keyExtractor和keyComparator,我们再来重新装配一下Comparator:

Comparator byFirstNameAdvanced = (o1, o2) ->

keyComparator.compare(keyExtractor.apply(o1), keyExtractor.apply(o2));

到了这一步,我们牺牲了简洁性,但获得了相应的灵活性,也就是说,如果我们改变比较键为姓而非名,只需改动keyExtractor为:

Function keyExtractor = o -> o.getLastName().charAt(0);

值得庆幸的是,库的设计者考虑到了这一自然比较的需求的普遍性,因此为Comparator接口提供了静态方法comparing(...),只需传入比较键的提取规则,就能针对该键生成相应的Comparator,是不是非常神奇:

Comparator compareByFirstName = Comparator.comparing(keyExtractor);

即使我们想改变比较的规则,比如比较联系人姓与名的长度,也只需做些许改动:

Comparator 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 contacts = new ArrayList() {{

add(new Contact().setFirstName("Foo").setLastName("Jack"));

add(new Contact().setFirstName("Bar").setLastName("Ma"));

add(new Contact().setFirstName("Olala").setLastName("Awesome"));

}};

Comparator byFirstName = new Comparator() {

@Override

public int compare(Contact o1, Contact o2) {

return Character.compare(o1.getFirstName().charAt(0), o2.getFirstName().charAt(0));

}

};

//--- Using Lambda form ---//

Comparator byFirstNameLambdaForm = (o1, o2) ->

Character.compare(o1.getFirstName().charAt(0), o2.getFirstName().charAt(0));

Function keyExtractor = o -> o.getFirstName().charAt(0);

Comparator keyComparator = (c1, c2) ->

Character.compare(c1, c2);

Comparator byFirstNameAdvanced = (o1, o2) ->

keyComparator.compare(keyExtractor.apply(o1), keyExtractor.apply(o2));

Comparator compareByFirstName = Comparator.comparing(keyExtractor);

Comparator 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表达式并行与组合行为,希望对大家有所帮助,如果大家有任何疑问请给我留言,会及时回复大家的。在此也非常感谢大家对我们网站的支持!


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:Java Lambda表达式之从集合到流
下一篇:SVG描边动画
相关文章

 发表评论

暂时没有评论,来抢沙发吧~