|
| 1 | +--- |
| 2 | +id: refactoring.loops.withsteps |
| 3 | +title: Converting Loops with Steps |
| 4 | +slug: learn/refactoring-to-functional-style/loopswithsteps |
| 5 | +type: tutorial-group |
| 6 | +group: refactoring-to-functional-style |
| 7 | +layout: learn/tutorial-group.html |
| 8 | +subheader_select: tutorials |
| 9 | +main_css_id: learn |
| 10 | +toc: |
| 11 | +- Iterating with Steps {steps} |
| 12 | +- From Imperative to Functional Style {imperativetofunctional} |
| 13 | +- Unbounded Iteration with a break {break} |
| 14 | +- Mappings {mappings} |
| 15 | +description: "Converting Imperative Loops with Steps to Functional Style." |
| 16 | +last_update: 2023-07-06 |
| 17 | +author: ["VenkatSubramaniam"] |
| 18 | +--- |
| 19 | + |
| 20 | +<a id="steps"> </a> |
| 21 | +## Iterating with Steps |
| 22 | + |
| 23 | +In the previous article in this series we looked at converting simple loops written in the imperative style to the functional style. In this article we'll see how to take on loops that are a bit more complex—when we have to step over some values in an interval. |
| 24 | + |
| 25 | +When looping over a range of values, one at a time, the `range()` method of `IntStream` came in handy to implement in the functional style. This method returns a stream that will generate one value at a time for values within the specified range. At first thought, to skip some values we may be tempted to use the `filter()` method on the stream. However, there's a simpler solution, the `iterate()` method of `IntStream`. |
| 26 | + |
| 27 | +<a id="imperativetofunctional"> </a> |
| 28 | +## From Imperative to Functional Style |
| 29 | + |
| 30 | +Here's a loop that uses step to skip a few values in the desired range: |
| 31 | + |
| 32 | +```java |
| 33 | +for(int i = 0; i < 15; i = i + 3) { |
| 34 | + System.out.println(i); |
| 35 | +} |
| 36 | +``` |
| 37 | + |
| 38 | +The value of the index variable `i` starts at `0` and then is incremented by `3` as the iteration moves forward. When you find yourself looking at a loop like that where the iteration is not over every single value in a range, but some values are skipped, consider using the `iterate()` method of `IntStream`. |
| 39 | + |
| 40 | +Before we refactor the code, let's take a closer look at the `for()` loop in the previous code, but with a pair of imaginary glasses that let us look at potential uses for lambdas. |
| 41 | + |
| 42 | +```java |
| 43 | +//imaginary code |
| 44 | +for(int i = 0; i < 15; i = i + 3) //imperative |
| 45 | +for(seed, i -> i < 15, i -> i + 3) //functional |
| 46 | +``` |
| 47 | + |
| 48 | +The first argument passed to the `for` loop is the starting value or the seed for the iteration and it can stay as is. The second argument is a predicate that tells the value of the index variable, `i`, should not exceed the value of `15`. We can replace that in the functional style with a `IntPredicate`. The third argument is incrementing the value of the index variable and that, in functional style, is simply a `IntUnaryOperator`. The `IntStream` interface has a `static` method named `iterate()` that nicely represents the imaginary code: `iterate(int seed, IntPredicate hasNext, IntUnaryOperator next)`. |
| 49 | + |
| 50 | +Let's refactor the loop to use functional style. |
| 51 | + |
| 52 | +```java |
| 53 | +import java.util.stream.IntStream; |
| 54 | + |
| 55 | +... |
| 56 | +IntStream.iterate(0, i -> i < 15, i -> i + 3) |
| 57 | + .forEach(System.out::println); |
| 58 | +``` |
| 59 | + |
| 60 | +That was pretty straightforward, the `;`s became `,`s, we made use of two lambdas: one for the `IntPredicate` and the other for the `IntUnaryOperator`. |
| 61 | + |
| 62 | +In addition to stepping over values, we often use an unbounded loop and that throws a bit more complexity on us, but nothing the functional APIs of Java can't handle, as we'll see next. |
| 63 | + |
| 64 | +<a id="break"> </a> |
| 65 | +## Unbounded Iteration with a break |
| 66 | + |
| 67 | +Let's take a look at the following imperative style loop which, in addition to the step, is unbounded and uses the `break` statement. |
| 68 | + |
| 69 | +```java |
| 70 | +for(int i = 0;; i = i + 3) { |
| 71 | + if(i > 20) { |
| 72 | + break; |
| 73 | + } |
| 74 | + |
| 75 | + System.out.println(i); |
| 76 | +} |
| 77 | +``` |
| 78 | + |
| 79 | +The terminating condition of `i < 15` is gone and the loop is unbounded as indicated by the repeated `;;`s. Within the loop, however, we have the `break` statement to exit out of the iteration if the value of `i` is greater than `20`. |
| 80 | + |
| 81 | +For the functional style, we can get rid of the second argument, the `IntPredicate` from the `iterate()` method call but that will turn the iteration into an infinite stream. The functional programming equivalent of the imperative style `break` is the `takeWhile()` method. This method will terminate the internal iterator, the stream, if the `IntPredicate` passed to it evaluates to `false`. Let's refactor the previous imperative style unbounded `for` with `break` to functional style. |
| 82 | + |
| 83 | +```java |
| 84 | +IntStream.iterate(0, i -> i + 3) |
| 85 | + .takeWhile(i -> i <= 20) |
| 86 | + .forEach(System.out::println); |
| 87 | +``` |
| 88 | + |
| 89 | +The `iterate()` method is overloaded and comes in two flavors, one with the `IntPredicate` and the other without. We made use of the version without the predicate to create an infinite stream that generates values from the seed or the starting value. The `IntUnaryOperator` passed as the second argument determines the steps. Thus, in the given code example, the stream will generate values `0`, `3`, `6`, and so on. Since we want to limit the iteration so that the index does not exceed the value of `20` we use the `takeWhile()`. The predicate passed in to `takeWhile()` tells that the iteration may continue as long as the value of the parameter given, the index `i`, does not exceed the value of `20`. |
| 90 | + |
| 91 | +We saw in the previous article that `range()` and `rangeClosed()` are direct replacement for the simple `for` loop. If the loop gets a bit more complex, no worries, Java has you covered, you can use the `IntStream`'s `iterate()` method and optionally the `takeWhile()` if the loop is terminated using `break`. |
| 92 | + |
| 93 | +<a id="mappings"> </a> |
| 94 | +## Mappings |
| 95 | + |
| 96 | +Anywhere you see a `for` loop with step, use the `iterate()` method with three arguments, a seed or the starting value, a `IntPredicate` for the terminating condition, and a `IntUnaryOperator` for the steps. If your loop uses the `break` statement, then drop the `IntPredicate` from the `iterate()` method call and instead use the `takeWhile()` method. The `takeWhile()` is the functional equivalent of the imperative style `break`. |
| 97 | + |
0 commit comments