Follow us on:

Tail recursion java

tail recursion java com/2018/06/09/tail-recursion-in-java-8/. Banerjee. This is not necessarily the most efficient version of map both in terms of memory usage and speed but it’s a good illustration of ES6. In other words, the return is a function. In response, modern Java IDE's will allow you to detect direct tail recursion, and offer to replace it with equivalent iterative code automatically. Tail recursion does the same opposite of this; it makes your calculations, then it passes the result to the other call, until the result is calculated. The goal is to have the following result: This Java tutorial for beginners explains and demonstrates head recursion and tail recursion. This will make the calculation occurs before the recursion call and hence there is no need to keep the stack to store the intermediate value when it moves to the next recursive call . We can use factorial using recursion In many functional programming languages such as Haskell or Scala, tail recursion is an interesting feature in which a recursive function calls itself as the last action. com Tail recursion in Java There’s another way to implement the recursive version of the factorial. Why tail calls? More examples. result;}} Sometimes we choose tail recursion for the performance improvement which can be achieved by a language implementation optimization. jct. To see how tail recursion is used, see this example: Aniruddha Sadhukhan. g. Tail-Call Optimisation (TCO) lets us convert regular recursive calls into tail calls to make recursions practical for large inputs, which was earlier leading to stack overflow error in normal recursion scenario. Tail recursion follows one rule for implementation. We can understand that term in parts. To get the correct intuition, we first look at the iterative approach of calculating the n-th Fibonacci number. Other languages, e. Most of the frame of the current procedure is no longer needed, and can be replaced by the frame of the tail call, modified as appropriate (similar to overlay for processes, but for function calls). Updated on Feb 15, 2017. 2. We have the answer without ever having to do a pop, and if we don't need to do any pops, we didn't need to remember the stuff in the first place. This function … Write a tail recursive function for calculating the n-th Fibonacci number. Tail Calls. This rule is as follow: The recursive call must be the last call of the method. When one function is called, its address is stored inside the stack. By Dave Griffith at Thu, 2005-05-19 19:45 | login or register to post comments The tail. See full list on github. This technique is well known to the people who work on compiler implementations. Tail Recursion. The tail-recursive version was almost twice as fast ad the body-recursive one. be/Sf-LR7OI See full list on drdobbs. Call the factorial function, passing in n – 1 and soFar. First this is the normal recursion: Java doesn't have tail call optimization for the same reason most imperative languages don't have it. Thus the structure of the resulting program closely mirrors that of the grammar it recognizes. Review the reading on tail recursion. For the reasons of stack overflows, it’s pretty important to mention. In computer science, a recursive descent parser is a kind of top-down parser built from a set of mutually recursive procedures (or a non-recursive equivalent) where each such procedure implements one of the nonterminals of the grammar. Java is still faster than Groovy tail-recursive function. Moreover, the recursive call must not be composed with references to memory cells storing previous values (references other than the parameters of the function). Tail / Bottom Recursion. This method takes a recursive state before the next iteration. Sometimes the tail recursive version is the cleaner implementation. The performance of this iterative function is equivalent to its recursive function. ac. Unfortunately, Java does not implement TCE. 2. java, recursion, tail-recursion / By cs777 I understand what tail recursion is but I am having trouble converting this method to tail recursion. Browse other questions tagged java recursion series or ask your own question. Remember though, recursion is repetition without a loop. A recursive function is tail recursive when the recursive call is the last thing executed by the function. Exercise 1: Identifying Tail-Recursive Procedures. printf(n);} //The function ends with a recursive call tail_recur(n-1);} Nested Recursion: It can be basically defined as “recursion within the recursion. Avoid recursion. I think this is a reasonable compromise between efficiency and code readability. With tail recursion, you can incorporate the scalability of loops. Avoid recursion. The fact that a tail-recursive function can be optimized to not unnecessarily-allocate stack space is a compiler-implementation issue — albeit it’s what makes the concept of tail-recursion important. Thus the structure of the resulting program closely mirrors that of the grammar it recognizes. Output: The inner function fibonacci() is a tail recursive function as it has its own function call as it’s last action. Recursion can be replaced by iteration with an explicit call stack, while iteration can be replaced with tail_recursion. g. S. In other words, tail call optimization means that it is possible to call a function from another function without growing the call stack. This makes the recursive call equivalent to looping, and avoids the risk of stack overflow. A recursive function call is said to be tail recursive if there is nothing to do after the function returns except return its value. As I’ve mentioned before, it’s pretty important to think about the stack when you do recursion. Happy learning. What is tail recursion? A recursive function is tail recursive when recursive call is the last thing executed by the function. That’s the thing, is a lot of languages these days do not have tail call removal. Tail calls can be implemented without adding a new stack frame to the call stack . Java Program to Find G. The solution is to use tail calls, or tail recursion, so that the stack doesn’t crash. The best way to figure out how it works is to experiment with it. Well there is another type of recursion called tail recursion, which if optimized for, can avoid stack overflow errors. java meets the minimum recursion required (not tail-recursive, different from our examples, and uses transformations in the recursion), it will be eligible for extra credit. In head recursion, the recursive call, when it happens, comes before other processing in the function (think of it happening at the top, or head, of the function). “i” holds starting element index and “j” holds ending element index of the array. - Implement Appropriate Methods Like Add, IsLast(), Findlength(), GetFirst(), SetFirst(), GetTail(). As there is no task left after the recursive call, it will be easier for the compiler to optimize the code. package recursion; import java. It turns out that most recursive functions can be reworked into the tail-call form. In functional languages like Scheme, iteration is defined as tail recursion. If a function is tail recursive, it’s either making a simple recursive call or returning the value from that call. A recursive function is tail recursive when the recursive call is the last thing executed by the function. Tail recursion is a special case of recursion where a function calls itself via a tail call. argument);} while (e instanceof More); return ((Done<?, Out >) e). So the kind of recursion that we just saw was head recursion. Tail recursion. Tail recursion is also used to return the value of the last recursive call as the value of the function. function. 1 (both 3rd and 4th eds). The Overflow Blog Level Up: Creative coding with p5. · In Java a method can call itself --> if written that way, the method is called a recursive method. It is an efficient method as compared to others, as the stack space required is less and even compute overhead will get Iteration and recursion are both ways to achieve repetition in programs. See full list on baeldung. concurrent Update the original printList function / method to call one of the recursive versions instead, passing the head of the list to the recursive version. Tail recursion is similar to a loop in that it performs all its computation before performing the next recursive call; Head Recursion – any recursive function which is not Tail Recursive. Author: Dipta P. knoldus. f = f;} public Out run (In argument) {Result< In, Out > e = new More (argument); do {e = f. Suggestion: Don’t immediately delete the code from this function. The fact that a tail-recursive function can be optimized to not unnecessarily-allocate stack space is a compiler-implementation issue — albeit it’s what makes the concept of tail-recursion It is better than the normal (non-tail) recursion as the compiler is able to optimize it, i. Recursion can be categorized as either Head Recursion or Tail Recursion, depending on where the recursive method call is placed. Reverse is an example where the right way to build it is tail recursive. Think about it. One can be converted to the other: All iterative functions can be converted to recursion because iteration is just a special case of recursion (tail recursion). 6. Function; public class TailRecursion <In, Out> {private Function< In, Result< In, Out > > f; public TailRecursion (Function< In, Result< In, Out > > f) {this. So, we can say that a function is tail recursive if the final result of the recursive call – in this case 24 – is also the final result of the function itself, and that is why it’s called “tail” recursion – because the final function call (the tail-end call) actually holds the final result. Haskell and Scheme, are able to do automatic TCO and so tail recursive functions are not stack-consuming. a. A tail-recursive function is just a function whose very the last action is a call to itself. However, it is being called on a different object . out. This approach consists in having the recursive call executed just before to return from the function. Checks for 0, 1, 2 and returns 0, 1, 1 accordingly because Fibonacci sequence in Java starts with 0, 1, 1. A recursive method is tail recursive when recursive method call is the last statement executed inside the method (usually along with a return statement). Aligned to AP Computer Science A. concurrent You can ask me : “But tail-recursion do the same think, and it’s faster”. Otherwise, the recursion stops there. Tail recursion is a mechanism by which the recursive stack frames are reused, so they don’t occupy additional stack memory. If your language supports such optimisations, you might be fine. S. If we pass in a value of 3 into our tail recursive function, the sequence of function calls would look like this: factorial(3, 1)return factorial (3, 1 * 3 ) //i == 3return factorial (2, 3 *2 ) //i == 2return 6 //i == 1. - [Instructor] In the last section…we saw how a recursion called stack works. A natural way I can think of is to Recursion is a basic programming technique you can use in Java, in which a method calls itself to solve some problem. Reading: Scott also discusses recursion and tail-recursion, in §6. Recursion Types. util. Tail recursion is a compile-level optimization that is aimed to avoid stack overflow when calling a recursive method. We know that in normal recursion, a function calls itself repeatedly until the exit condition is met. Example: [code]void Node::traverse() { if (left_ != nullptr) { left_->traverse(); } if (right_ != nullptr) { right_->traverse(); } } [/code]Tail recursion can be Your recursive method does not need to be especially complex to create a very cool design. Contents. Start DrScheme. Recursion is the technique of making a function call itself. This time, the recursive call is the last step in our process. A tail-recursive functiontakes only single space in the stack as compared to the memory required by normal recursion that takes one stack space for each recursion. In the process, the problem is solved by reducing it to smaller versions of itself. It is also a statement that returns the calling function. Recursion can be categorized as either Head Recursion or Tail Recursion, depending on where the recursive method call is placed. As long as “i” is less than “j”, we swap two elements starting and ending element of the array. Increment “i” and decrement “j” and solve the similar problem for the rest of the array until two in-variants “i” and “j” cross over. Modern functional programming languages like Scala encourage the use of recursion, as they offer means of optimising tail-recursing algorithms back into iterative ones. A recursive function call is tail recursive when recursive call is the last thing executed by the function. Scheme, ECMAScript), and even more languages have Proper Tail Recursion (e. For instance, let's say we want to sum integers from 1 to i. …Now can there be a way…to avoid stack overflow kind of errors?…Think about it. Please leave a reply in case of any queries. util. Examples : Input : n = 4 Output : fib(4) = 3 Input : n = 9 Output : fib(9) = 34 Prerequisites : Tail Recursion, Fibonacci numbers. Tail recursions are recursions where the recursive call is the last line in the method. However, for deep recursion, sometimes an iterative solution can consume less of a thread's finite stack space. In tail recursion the In tail recursion, calculations are performed first, then recursive calls are executed (the recursive call passes the result of your current step to the next recursive call). Current Java compilers (up to and including Java 9) do not perform tail-call elimination. Via Lambda the Ultimate , I learned of Rich Hinkey's implementation of trampolining in Clojure to make tail optimized mutual recursion possible. Imperative loops are the preferred style of the language, and the programmer can replace tail recursion with imperative loops. The call is done two times. A console based application to calculate factorial using Tail-Call-Optimisation. Definition: A function is tail recursive if its output expression in every recursive case is only the recursive call. This eliminates the potential problem of stack overflow. A method that contains a tail call is said to be tail recursive. It makes recursion a lot more practical for your language. This lesson covered the basics of converting a simple recursive function into a tail-recursive function. In Tail recursion the computation is done at the beginning before the recursive call. . In general languages like Java, C, and Python, recursion is fairly expensive compared to iteration because it requires the allocation of a new stack frame. com Post Views: 246. Java still doesn’t support tail optimization… 😞 (Definition) Tail-optimized design is when every last possible statement (typically a return statement) in a function is a just the recursive call (or the end case value). If you have tail call removal in your language, then boom, you have…It’s basically an optimization. It’s recursion in the tail call position. import java. b. A recursive result-bearing ForkJoinTask. com The tail recursion is better than non-tail recursion. That is, the function returns only a call to itself. Tail-recursion is a form of recursion in which the recursive calls are the last instructions in the function (that's where the tail part comes from). Writing a tail recursion is little tricky. Tail recursion and loops. g. To declare a recursion as tail recursion we need to use tailrec modifier before the recursive function. Java program to print Fibonacci series of a given number. For example, check out a basic recursive function that counts Some programming languages are tail-recursive, essentially this means is that they're able to make optimizations to functions that return the result of calling themselves. In Java it will still end with StackOverflowError it would be nice to show some stop condition. Recursion in Java is when a method calls itself within it own definition. - Set The Value Field Of The Index-th Node In The List To The Element Parameter. So the kind of recursion that we just saw was head recursion. Here's the tail-recursive version of factorial. Spring 1996. Tail recursion. Tail recursion. A recursive method may be more concise than an equivalent non-recursive approach. A recursive result-bearing ForkJoinTask. Tail recursion is the act of making a tail recursive call. nodejs javascript console algorithm wikipedia stackoverflow subroutines data-structures tail-calls tail-recursion factorial recursive-algorithm tail-call-optimization. For example, the following implementation of Fibonacci numbers is recursive tail recursion is only valuable when compiler supports it (tail call optimization). This code looks awful and there are many places where you can make a mistake. We will discuss the various methods to find out the Fibonacci Series In Java Program for the first n numbers. For example, we have a recursive function that calculates the greatest common divisor of two numbers in Scala: What Is Recursion In Java? Recursion is a process by which a function or a method calls itself again and again. A function is recursive if it calls itself. js – part 3 It is useful to notice when ones algorithm uses tail recursion because in such a case, the algorithm can usually be rewritten to use iteration instead. When input n is >=3, The function will call itself recursively. FROM: https://blog. Why tail recursion? With tail recursion it's easy for the compiler to remove the recursion and drop the growing stack. C. Recommended: Please try your approach on {IDE} first, before moving on to the solution. The complexity isn't worth it for a feature whose use is discouraged as a matter of style. The methods as aforementioned are: Using For Tail recursion is just recursion in the tail call position. Tail-Call Optimisation (TCO) lets us convert regular recursive calls into tail calls to make recursions practical for large inputs, which was earlier leading to stack overflow error in normal recursion scenario. …Well there is another type of recursion…called tail recursion,…which if optimized for, can avoid stack overflow errors. We as a programmer should create a balance between easy and clean writing of code with memory and time optimization. The Overflow Blog Level Up: Creative coding with p5. For example, here we have a tail call: function doA (a) {. . 🔥 Subscribe To Get More Tutori Tail-call optimization is a valuable tool that converts tail recursion to a form that rivals conventional iteration for efficiency; it applies to nonrecursive tail calls as well. Create recursive function We’ll create a function that executes as follows: The Java Fibonacci recursion function takes an input number. com When N = 20, the tail recursion has a far better performance than the normal recursion: Update 2016-01-11. Summary. Then at the end of the function—the tail—the recursive case runs only if the base case hasn't been reached. Yes, because the recursion will open each time a new function without closing the last one until the last recursive See full list on programiz. take less time and memory while executing the recursion. So with an optimized tail recursive function you will not run out of stack Java is unable to do tail-call optimisation (TCO), so in Java all recursive functions are stack-consuming. The Plan for our Tail Recursive Tree Traversal Algorithm. Tail Recursion A recursive function is said to be tail recursive if there are no pending operations to be performed on return from a recursive call. When we are looking at recursing algorithms, a useful distinction is Head Recursion and Tail Recursion. Tail recursion. The function checks for the base case and returns if it's successful. If your language supports such optimisations, you might be fine. This can only happen when recursive calls are the last expressions on their code path. 1、 Tail call: Tail call refers to the last statement of a function. In Tail Recursion, the recursion is the last operation in all logical branches of the function. You can see that the last method call is tail recursive. Converting recursive functions to tail-recursive ones; Invariants; Turning tail-recursive functions into loops; if as a function. For a classic example, here is a task computing Fibonacci numbers: Methods inherited from class java. return a; A method call occurring in last position, meaning as the last thing to do before returning, is called a tail call. So, for working out a tail recursive method of traversing trees, we’ll walk through several steps: Start off with a recursive algorithm that is rather naive (i. Idea: In most programming languages (like C or Java) function calls are implemented by pushing an activation record on a stack. recursion basics with examples: https://youtu. This topic includes examples of recursion in Java. For every function invocation, a new frame is created on the call stack. To understand this example, you should have the knowledge of the following Java programming topics: Java Recursion. In tail recursion, the recursive step comes last in the function—at the tail end, you might say. In fact, that’s one of the 7 myths of Erlang performance. Example. As long as your Art. In Head Recursion, we call ourselves first and then we do something about the result of recursion. Avoiding pushing the environment onto the stack to resume method processing after a tail call is an optimization technique known as Tail Call Elimination (TCE). This can impact the performance of recursive algorithms, and if the recursion is deep enough, it can lead to StackOverflowError crashes; see Deep recursion is problematic in Java. Such functions, immediately return the return value from the calling function. …The data structure may vary according to our needs. …So the kind of recursion that we just saw…was head recursion. Let’s see the Fibonacci Series in Java using recursion example for input of 4. A function like this is called tail recursive . So, you can see that each time the factorial function is called in our example, a new value for the current_factorial variable is passed into the factorial function. Normal recursion. The compiler has been added so that you can execute the set of programs yourself, alongside suitable examples and sample outputs. Java still doesn’t support tail optimization… 😞 (Definition) Tail-optimized design is when every last possible statement (typically a return statement) in a function is a just the recursive call (or the end case value). Identify three (or more) tail-recursive procedures you've already written. not tail recursive) Convert that algorithm to it’s tail recursive equivalent; The naive Implementation To make tail recursion possible, I need to think about the problem differently. This function that is called again and again either directly or indirectly is called the “recursive function”. apply(((More< In, ?>) e). util. Scala, at least for Direct Tail Recursion). Your code is equivalent to loop running forever. A tail recursion is also a kind of recursion but it will make the return value of the recursion call as the last statement of the method. There must have no other instruction to execute between the recursive call and the return instruction. The activation record is used for keeping track of local variables, return values, and other things necessary for keeping track of data local to a function call. Example: int tail_recur(int n) {if(n=0) {System. length === 0 checks whether there is still a tail to recurse over. So if it is tail recursion, then storing addresses into stack is not needed. e. In head recursion , the recursive call, when it happens, comes before other processing in the function (think of it happening at the top, or head, of the function). e. Confusing, I know, but stick with me. 1. This programming concept is often useful for self-referencing functions and plays a major role in programming languages such as LISP. When you call an F# function, stack space is allocated and then freed when the function returns, or, when a tail call is performed. Comp 210. Find the factorial of any number with the use of tail recursion In this article we are going to learn how to use tail recursion and also implement it to find the factorial of the number ? Submitted by Manu Jemini , on January 13, 2018 Tail recursion refers to recursive call at last line. il In order to understand recursion, you must understand recursion. The Tale of ‘Tail Recursion’ - DZone Java 6. Example 1: Sum of Integers. Procedure pd, to the right, is tail recursive because the last statement in its body, pd(n-1), pis a tail call. Factorial program in Java using recursion. Is tail-call recursion always faster? While the results of that benchmark look quite convincing, tail-recursion isn’t always faster than body recursion. Tail recursion (or tail-end recursion) is particularly useful, and often easy to handle in implementations. Exercise 2: Watching Tail Recursion. Java program to display a Fibonacci Series. In this video, we will learn head recursion, tail recursion and head vs tail recursion with example. P. Recursive functions can be used to solve tasks in elegant ways. Modern functional programming languages like Scala encourage the use of recursion, as they offer means of optimising tail-recursing algorithms back into iterative ones. Tail recursions are generally considered a bad practice and should be replaced with Iteration. In computer science, a recursive descent parser is a kind of top-down parser built from a set of mutually recursive procedures (or a non-recursive equivalent) where each such procedure implements one of the nonterminals of the grammar. P. A tail-recursive function is just a function whose very the last action is a call to itself. This technique provides a way to break complicated problems down into simple problems which are easier to solve. Code To Implement Reverse 6. It saves the current function’s stack frame is of no use. We can also add a variable named tail referencing the last element of the list (and update it when adding/removing elements from the end). lang. See full list on danzig. D Using Recursion In this program, you'll learn to find the GCD (Greatest Common Divisor) or HCF using a recursive function in Java. We will see various examples to understand recursion. I am trying to count how many adjacent elements there are in a matrix. Use it as a guide for setting up the recursive versions. Before Java 8 was released, recursion had been used frequently over loops to improve readability and problems, such as Fibonacci, factorial, or Ackermann that make use of this technique. Java Program for nth multiple of a number in Fibonacci Series; How to implement the Fibonacci series using lambda expression in Java? Java program to print the fibonacci series of a given number using while loop In a recursive method, a recursive call is called a tail call if it is the final action in the method —the method returns immediately after this call. Tail recursion implementation via Scala: The interesting thing is, after the Scala code is compiled into Java Byte code, compiler will eliminate the recursion automatically: Tail Recursion in ABAP. It does so by eliminating the need for having a separate stack frame for every call. For a classic example, here is a task computing Fibonacci numbers: Methods inherited from class java. Reversing an array using Recursion is an example of Tail Recursion . For example the following C++ function print () is tail recursive. Let’s say I want to find the 10th element in Fibonacci sequence by hand. Recursion are of two types based on when the recursive method call is made. out. Note: Note that being tail-recursive is a property of a function’s source-code. Defined tail recursion; Introduced the @tailrec annotation; Showed how to write a tail-recursive function; Showed a formula you can use to convert a simple recursive function to a tail-recursive function; What’s next. We maintain two in-variants “i” and “j”. Note:To solve a problem we can use iteration or recursion or even both. ”This signifies that one of the parameters of the initial recursive function is computed with the help of Many languages have Proper Tail Calls (e. In functional programming languages that don’t use normal iteration, the tail recursion (also known as tail call) becomes equivalent to loops. Unfortunately, neither C nor Java support Proper Tail Calls or even the much weaker Proper Tail Recursion, so, for large enough values of x, you will blow the stack, there is no way around it. Recursion may be a bit difficult to understand. In fact, the compiler will (or at least should) convert the recursive program into an iterative one. Some concepts of tail call, tail recursion and non tail recursion and whether non tail recursion can be converted into tail recursion. Well there is another type of recursion called tail recursion, which if optimized for, can avoid stack overflow errors. The first one offers the best performance, while the second one allows using tail-recursive constructs in your code with just a small (and in most cases acceptable) performance cost. Tail recursion means, the last call in the recursive function is a call to the function and there is nothing left to do when the recursive call returns. Therefore the next call would need to have a lock on this , and on the new MyClass() instance both. Our function would require constant memory for execution. A function is said to be tail-recursive, if no operations are pending when the recursive function returns to its caller. So we can We can thus say that a Tail Recursive function has no effect on performance in Java, whereas Scala compiler will optimize tail recursive functions based on the condition that the code ensures that function is not overridden in sub classes. Joke aside, recursion is a programming technique allowing to loop execution without using for or while, but using a function that calls itself. It is possible to eliminate this overhead in C/C++ enabling compiler optimization to perform tail recursion, which transforms certain types of recursion (actually, certain types of tail Question: - Creat Singly LinkedList Data Structure In Java. · Definition: Recursion = A problem solving / programming technique in which a method (function) can call itself in order to solve the problem. This is a great collection of programs of different programming languages like C,C++,Java,Python etc and hardware definition language like VHDL and covering topics like data structures,algorithms,numerical methods etc. …So we can write a program using tail Very often, to implement tail recursion, a helper method is used. Browse other questions tagged java recursion series or ask your own question. View full document. Tail Recursion: With respect to the order of statements in a function, if the recursive call is the last statement of the code, it is said that you have implemented tail recursion. No computation is performed on the returned value. Tail recursion is the act of calling a recursive function at the end of a particular code module rather than in the middle. Leaf procedures offer special opportunities for improvement because the callee can omit major portions of the standard linkage sequence. In recursion the computation is done after the recursive call, the example of factorial we have seen above is an example of recursion or head recursion where to calculate the factorial of n we need the factorial of n-1. A tail-recursive function is one where after making its recursive call. Due to this, tail recursive function can be represented as a loop. - The Class Has Only Two Fields, T Value, And Tail With A Constructor Where The Tail Is Null. js – part 3 Java and Tail-call elimination#. Recursion in Java. For simple recursion, the recur special form takes care of the problem, but wouldn't work for mutual recursion. The tail recursive functions considered better as the recursive call is at the last statement so there is nothing left to do in the current function. Recursions usually don't scale well, with devs falling back to iterative solutions. Terms: Recursion is a programming term that means calling a function from itself. Math; public class Recursion { // Sum up to N public static int sum_up (int n) { // Base case if (n <= 0 ) { return 0; } return n + sum_up (n-1); } // Tail-recursion public static int sum_up_tail (int n) { return sum_up_tail (n, 0); } public static int sum_up_tail (int n, int x) { if (n < 1) { return x; } else { return sum_up_tail (n-1, x + n); } } // Print up to n public static void print_up_to (int n) { print_up_to (n, 1); System. Once we have a recursive function that is in the correct form, we instruct Kotlin to consider it for tail recursion by use of the tailrec keyword. tail recursion java