Previous in the Series
Current Tutorial
Converting Loops with Steps
Next in the Series

Previous in the Series: Converting Simple Loops

Next in the Series: Converting foreach with if

Converting Loops with Steps

This page was contributed by Venkat Subramaniam under the UPL

 

Iterating with Steps

In the previous article in this tutorial 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.

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.

 

From Imperative to Functional Style

Here's a loop that uses step to skip a few values in the desired range:

for(int i = 0; i < 15; i = i + 3) {
  System.out.println(i);
}

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.

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.

//imaginary code
for(int i = 0; i < 15; i = i + 3) //imperative
for(seed, i -> i < 15, i -> i + 3) //functional

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).

Let's refactor the loop to use functional style.

import java.util.stream.IntStream;

...
IntStream.iterate(0, i -> i < 15, i -> i + 3)
  .forEach(System.out::println);

That was pretty straightforward, the ;s became ,s, we made use of two lambdas: one for the IntPredicate and the other for the IntUnaryOperator.

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.

 

Unbounded Iteration with a break

Let's take a look at the following imperative style loop which, in addition to the step, is unbounded and uses the break statement.

for(int i = 0;; i = i + 3) {
  if(i > 20) {
    break;
  }

  System.out.println(i);
}

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.

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.

IntStream.iterate(0, i -> i + 3)
  .takeWhile(i -> i <= 20)
  .forEach(System.out::println);

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.

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.

 

Mappings

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.


Last update: July 6, 2023


Previous in the Series
Current Tutorial
Converting Loops with Steps
Next in the Series

Previous in the Series: Converting Simple Loops

Next in the Series: Converting foreach with if