在我们日常开发过程中,难免会遇到需要的递归的业务逻辑开发。
那么它的优点是什么呢?
:代码简单、直观、运算不重复
但是它有一个致命的缺点
:性能得不到保证、计算数据量大时,会造成栈溢出
当我们觉得我们使用递归完成业务后,一运行,发现了StackOverFlowError的错误,严重时会使得整个程序都会崩溃的问题。
我们来举一个简单的例子:
我们算一个数的阶乘
/** 阶乘计算
* @param args
*/
public static void main(String[] args) {
BigInteger n = new BigInteger("20000");
System.out.println("Factorial of " + n + " is: " + factorial(n));
}
public static BigInteger factorial(BigInteger n) {
if (n.compareTo(BigInteger.ONE) == 0) {
return n;
}
return n.multiply(factorial(n.subtract(BigInteger.ONE)));
}
然后我们来运行一下
Exception in thread "main" java.lang.StackOverflowError
at java.math.BigInteger.compareTo(BigInteger.java:3625)
at org.example.Factorial.factorial(Factorial.java:12)
at org.example.Factorial.factorial(Factorial.java:15)
at org.example.Factorial.factorial(Factorial.java:15)
。。。。
报错了,可想而知,在我们的业务中20000的阶乘预算其实并不是很大,就报错了。
那么我们该如何解决这样的错误呢?
我们都知道在Java8中,引入了lamdba函数式编程。
虽然Lambda表达式本身并不是为了递归而设计的,因为Java的Lambda表达式不支持递归调用,但是我们可以使用一些技巧使用Lambda完成递归。
话不多说,我们来实现一下:
1. 首先实现一个为递归的函数,函数内包含以下几个方法:
(这里可以使用java.util.function.Function,为了更好的表达解释,所以我们直观的写一个)
a.递归是否结束方法
b.获取递归结果方法
c.递归循环拼接方法
d.执行递归方法
/**
* 尾递归
* @param <T>
*/
@FunctionalInterface
public interface TailRecursion<T> {
/**
* 递归执行
*/
TailRecursion<T> apply();
/**
* 递归是否结束
*/
default boolean isFinished() {
return false;
}
/**
* 获得递归结果
*/
default T getResult() {
throw new Error("递归还没有结束,调用获得结果异常!");
}
/**
* 执行递归
*/
default T invoke() {
return Stream.iterate(this, TailRecursion::apply)
.filter(TailRecursion::isFinished)
.findFirst()
.get()
.getResult();
}
}
2. 再写一个类,来运行递归和结束递归。
/**
* 使用尾递归的类
*/
public class TailInvoke {
/**
* 运行下一个递归
*/
public static <T> TailRecursion<T> call(final TailRecursion<T> nextFrame) {
return nextFrame;
}
/**
* 结束递归
*/
public static <T> TailRecursion<T> done(T value) {
return new TailRecursion<T>() {
@Override
public TailRecursion<T> apply() {
throw new Error("递归已经结束,非法调用apply方法");
}
@Override
public boolean isFinished() {
return true;
}
@Override
public T getResult() {
return value;
}
};
}
}
最后,我们来改一下计算阶乘的代码,进行运行
/**
* 阶乘计算
*
* @param args
*/
public static void main(String[] args) {
BigInteger n = new BigInteger("80000");
BigInteger invoke = factorial(n, n.subtract(new BigInteger("1"))).invoke();
System.out.println(invoke);
}
public static TailRecursion<BigInteger> factorial(final BigInteger factorial, final BigInteger number) {
if (number.compareTo(BigInteger.ONE) == 0) {
return TailInvoke.done(factorial);
} else {
return TailInvoke.call(() -> factorial(factorial.multiply(number),number.subtract(BigInteger.ONE)));
}
}
这里我直接使用80000来运算,结果正常输出。
文章讲述了递归在开发中的优点和性能问题,特别是栈溢出。通过Java8的Lambda表达式和尾递归实现,作者展示了如何避免StackOverflowError,如使用`TailRecursion`接口和`TailInvoke`类进行阶乘计算的示例。

被折叠的 条评论
为什么被折叠?



