尾调用优化 java_基于Java8函数式编程求一个List的全部子集|尾调用优化解决递归性能问题...

目录

基于函数式编程求一个List的全部子集

代码来自《Java8 in Action》,思路和其他递归解决方法一致,但不同的地方在concat方法

public static List> subsets(List list){

if(list.isEmpty()) {

List> ans = new ArrayList<>();

ans.add(Collections.emptyList());

return ans;

}

Integer first = list.get(0);

List rest = list.subList(1, list.size());

List> subans = subsets(rest);

List> subans2 = insertAll(first,subans);

return concat(subans,subans2);

}

public static List> insertAll(Integer first,List> lists){

List> result = new ArrayList>();

for (List list : lists) {

List copyList = new ArrayList();

copyList.add(first);

copyList.addAll(list);

result.add(copyList);

}

return result;

}

public static List> concat(List> a,List> b)

{

List> r = new ArrayList>(a);

r.addAll(b);

return r;

}

乍一看,concat方法也没啥,但是这种写法涉及到思想的转变。

在Java8中,最主要的改变就是函数式编程,它接受零个或多个参数,生成一个或多个结果,并且不会有任何副作用,这里的副作用可以理解为对传入参数/其他数据源做了修改。

这里稍稍扩展下,为什么函数式编程不修改数据源,在并发编程中,往往是对某一数据源进行多方修改,所以要用锁,而锁的维护是很难的,很多程序的bug都出在这里。

函数式编程就提出不修改数据源,这样,出bug的可能性就不存在了。函数式编程默认变量是不可变的,如果要改变变量,需要把变量copy然后修改,就像上面的concat方法一样。

按照以往的思路concat方法可能会这么写:

public static List> concat(List> a,List> b){

//List> r = new ArrayList>(a);

a.addAll(b);

return a;

}

尾调用优化解决递归性能问题

整体思路还是递归那一套,使用递归必然带来性能问题,甚至StackOverflowError。

所以,我们通常是写迭代而不是递归,但是Java8提倡用递归取代迭代,因为迭代往往会修改数据源,而函数式编程不修改。

那么递归的性能问题怎么解决?

先来说下什么叫做尾调用。

尾调用就是在方法的最后调用。

这就是尾调。

public int getResult(int a,int b) {

return add(a,b);

}

而下面这两个方法都不是尾调。

public int getResult(int a,int b) {

return add(a,b)+5;

}

public int getResult(int a,int b) {

int c = add(a,b);

return c;

}

咱们再来回顾下递归性能问题产生的原因:

函数调用会在内存中生成调用帧,用以保存位置和内部变量,直到程序完全结束。递归一次就生成一个栈帧,如果输入量很大,直接就StackOverflowError了。

0233dce1e567399edc4cfcee968f3e03.png

图片来自阮一峰博客

拿阶乘举个例子:

平常的递归方法这么写:

public static int factorial(int n) {

if(n==1)

return n;

return n*factorial(n-1);

}

调用过程是这样的:

95c069c6361d7c570ca4c7207215a77c.png

把方法改成尾调用是这样的:

public static int factorial(int a,int n) {

if(n==1)

return a;

return factorial(a*n,n-1);

}

调用过程是这样的:

86973f25d314bf332bb8cca7b7128bff.png

尾调用因为是方法的最后一步调用,不需要保留之前的位置和状态,直接用内层函数的调用记录替换了外层调用记录。

本文分享 CSDN - w_boyang。

如有侵权,请联系 support@oschina.cn 删除。

本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值