Infinite Streams in Java 8 and 9
Time to put on your functional programming hats. See how Java 8 and 9 handle implementing infinite streams and check out the tools at your disposal.
Join the DZone community and get the full member experience.
Join For FreeWith the advent of lambdas and streams in Java 8, it's finally possible to write Java algorithms in a more functional style. An important element of functional programming is generated (or "infinite") streams that are cut by suitable conditions (of course, these streams aren't really infinite, but they are computed "lazily", i.e. on demand). Infinite streams do exist in Java, too, as we'll see in the following examples.
We're implementing the Luhn algorithm to calculate a simple checksum. I took the idea of using this example from the talk "JVM Functional Language Battle" by Falk Sippach. A short summary of the Luhn algorithm:
- Given a non-negative, integer number of arbitrary length.
- From right to left, double every second digit. If the resulting number has two digits, take the square sum (so we have one digit again).
- Sum up all the resulting digits.
- If the sum modulo 10 is zero, the original number is valid according to the Luhn algorithm.
How do we implement this algorithm with Java 8 streams? Without variables, state change or conditional branching? Like this, for the example:
import java.util.PrimitiveIterator;
import java.util.stream.IntStream;
public class Luhn {
public static boolean isValid(String number) {
PrimitiveIterator.OfInt factor =
IntStream.iterate(1, i -> 3 - i).iterator();
int sum = new StringBuilder(number).reverse()
.toString().chars()
.map(c -> c - '0')
.map(i -> i * factor.nextInt())
.reduce(0, (a, b) -> a + b / 10 + b % 10);
return (sum % 10) == 0;
}
}
Let's run isValid()
with parameter string "8763".
IntStream.iterate()
generates an endless (but lazily calculated) stream of numbers 1,2,1,2,1,2, ... – this is the factor we'll multiply each digit with.
Now, we wrap the parameter string in a StringBuilder since this class offers a reverse() method. This way we can walk through the chars() stream of digits forward:
'3', '6', '7', '8'
The firstmap()
converts each digit char to its number value. The second map()
multiplies this number value with the next factor value from the infinite IntStream
(i.e. here, each second digit is doubled):
3, 12, 7, 16
reduce()
calculates the square sum for all of these numbers (even for the ones with only one digit). We add the tens-digit (div 10) and the ones-digit (mod 10):
(0 + 3) + (1 + 2) + (0 + 7) + (1 + 6)
That gives 20, which is divisible by 10 without a remainder. So the checksum is valid!
Meanwhile, Java 9 introduced takeWhile()
. We can use this stream operation to get rid of the StringBuilder
and the reversing of the characters. Instead, we create a second infinite stream from the number
parameter and cut the stream with a suitable lambda predicate:
import java.util.PrimitiveIterator;
import java.util.stream.IntStream;
import java.util.stream.LongStream;
public class Luhn9 {
public static boolean isValid(long number) {
PrimitiveIterator.OfInt factor =
IntStream.iterate(1, i -> 3 - i).iterator();
long sum = LongStream.iterate(number, n -> n / 10)
.takeWhile(n -> n > 0)
.map(n -> n % 10)
.map(i -> i * factor.nextInt())
.reduce(0, (a, b) -> a + b / 10 + b % 10);
return (sum % 10) == 0;
}
}
The chars()
stream from the previous Java 8 example stopped after the last character of the parameter string. In this Java 9 example, LongStream.iterate()
divides the parameter by 10 endlessly, e.g.
8763, 876, 87, 8, 0, 0, 0, ...
We have to consider the stream's values only as long as they are greater than zero. takeWhile()
does exactly this and yields the following finite stream:
8763, 876, 87, 8
Now we map()
this stream to a stream of its rightmost digits (mod 10):
3, 6, 7, 8
The final operations (doubling of each second digit and adding the square sums) are the same as in the Java 8 example.
This implementation in Java might not be perfect functional style because we are limited to the existing Stream API and we cannot use composition of arbitrary functions (at least unless we're using additional libraries like Vavr). But hey, using only standard API, these examples show quite a different and more expressive Java already than we used to know!
[This text is also available in German on my personal blog.]
Published at DZone with permission of Thomas Much. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments