From what I have read: The reason is because it is not easy to determine which method will actually be called as we have inheritance.
However, why doesn’t Java at least have tail-recursion optimization for static methods and enforce proper way to call static methods with the compiler?
Why doesn’t Java have any support at all for tail-recursion?
I am not sure if there is any difficulty here at all.
Regarding the suggested duplicate, as explained by Jörg W Mittag1:
- The other question asks about TCO, this one about TRE. TRE is much simpler than TCO.
- Also, the other question asks about what limitations the JVM imposes on language implementations that wish to compile to the JVM, this question asks about Java, which is the one language that is not restricted by the JVM, since the JVM spec can be changed by the same people who design Java.
- And lastly, there isn’t even a restriction in the JVM about TRE, because the JVM does have intra-method GOTO, which is all that’s needed for TRE
1Formatting added to call out points made.
7
As explained by Brian Goetz (Java Language Architect at Oracle) in this video:
in jdk classes […] there are a number of security sensitive methods that rely on counting stack frames between jdk library code and calling code to figure out who’s calling them.
Anything that changed the number of frames on the stack would break this and would cause an error. He admits this was a stupid reason, and so the JDK developers have since replaced this mechanism.
He further then mentions that it’s not a priority, but that tail recursion
will eventually get done.
N.B. This applies to HotSpot and the OpenJDK, other VMs may vary.
7
Java doesn’t have tail call optimization for the same reason most imperative languages don’t have it. Imperative loops are the preferred style of the language, and the programmer can replace tail recursion with imperative loops. The complexity isn’t worth it for a feature whose use is discouraged as a matter of style.
This thing where programmers want to sometimes write in a FP-style in otherwise imperative languages only came into vogue in the last 10 years or so, after computers started scaling in cores instead of GHz. Even now, it’s not that popular. If I suggested replacing an imperative loop with tail recursion at work, half the code reviewers would laugh and the other half would give a confused look. Even in functional programming, you generally avoid tail recursion unless other constructs like higher-order functions don’t fit cleanly.
10
Java don’t have tall call optimization because the JVM does not have a bytecode for tail calls (to some statically unknown function pointer, e.g. a method in some vtable).
It looks like for social (and perhaps technical) reasons, adding a new bytecode operation in the JVM (which would make it incompatible with earlier versions of that JVM) is terribly difficult to the owner of the JVM spec.
Technical reasons for not adding a new bytecode in the JVM spec include the fact that real-life JVM implementations are terribly complex software pieces (e.g. because of the many JIT optimizations it is doing).
Tail calls to some unknown function requires replacing the current stack frame with a new one, and that operation should sit in the JVM (it is not only a matter of changing the bytecode generating compiler).
3
Unless a language has a special syntax for making a tail call (recursive or otherwise) and a compiler will squawk when a tail call is requested but cannot be generated, “optional” tail-call or tail-recursion optimization will yield situations where a piece of code may require less than 100 bytes of stack on one machine, but more than 100,000,000 bytes of stack on another. Such a different should be considered qualitative rather than merely quantitative.
It’s expected that machines may have differing stack sizes, and thus it’s always possible for code to work on one machine but blow the stack on another. Generally, however, code which will work on one machine even when the stack is artificially constricted will likely work on all machines with “normal” stack sizes. If, however, a method which recurses 1,000,000 deep is tail-call optimized on one machine but not another, execution on the former machine will likely work even it its stack is unusually small, and fail on the latter even if its stack is unusually large.
I’d think tail call recursion is not used in Java mainly because doing so would alter the stack traces and therefore make debugging a program much harder. I think one of Java’s primary goals is to allow programmers to easily debug their code, and stack tracing is essential to doing that especially in a highly object oriented programming environment. Since iteration could be used instead, the language committee must have figured it’s not worth adding tail recursion.