Your terminology is somewhat confusing. Both of the examples you showed are written in a functional style: there are no side-effects, no mutable state, no loops. Both examples are referentially transparent.
Also, you seem to be under the impression that only one of those examples will throw a StackOverflowError
. That is not the case. Both of those will eventually blow the stack.
In fact, in my testing, both of those blew the stack pretty much at the same value.
For the lambda version, I ran multiple tests, and the stack overflow happened at slightly different values each time, the smallest and biggest ones were around 11000 and around 15300.
For the method version, the stack overflow happened pretty consistently between 13901 and 13907.
Initially, I thought that the lambda version would consistently overflow earlier than the method version, because it uses much more complex runtime machinery (LambdaMetaFactory
, method handles, call sites, invokedynamic
) which increases the stack size. But, it looks like more than increase the stack size, it increases the variance due to its bigger reliance on runtime optimizations and heuristics.
By the way, your code (both versions) has the same two bugs (which are actually the same bug): it doesn't handle factorial of zero (which is one) and it runs into an infinite recursion for negative numbers. A more correct version would be something like:
private static Function<BigInteger, BigInteger> factorial =
x ->
x.compareTo(BigInteger.ZERO) < 0
? throw new ArgumentError()
: BigInteger.ZERO.equals(x)
? BigInteger.ONE
: x.multiply(App.factorial.apply(x.subtract(BigInteger.ONE)));
private static BigInteger factorial(BigInteger request) {
if (request.compareTo(BigInteger.ZERO) < 0) throw new ArgumentError;
if (BigInteger.ZERO.equals(request)) return BigInteger.ONE;
else return request.multiply(factorial(request.subtract(BigInteger.ONE)));
}