DEV Community

loading...

Java streams and Fibonacci

Bruno Zani
Tech stuff and personal notes
・4 min read

So, many people nowadays still struggling trying to understand expressions like this in Java 8+:

Stream.iterate(new long[]{ 0L, 1L }, p->new long[]{ p[1], p[0]+p[1] }).map(p -> p[0]).limit(10).map(p->p+" ").forEach(System.out::print);
Enter fullscreen mode Exit fullscreen mode

So tell me now, do you have a clue about what this line is about to do, when you hit Run? Yes, you're right, you'll have the Fibonacci sequence of 10 elements printed in your console, like this:

0 1 1 2 3 5 8 13 21 34
Enter fullscreen mode Exit fullscreen mode

I know, it's a tricky one, and if you didn't answer that correctly, I'll try something easier before digging into an explanation.

So, your second chance is right here:

Arrays.asList(1, 2, 2, 3, 4, 4, 4, 5, 6, 8).stream()
    .distinct()
    .filter(x -> x % 2 == 0)
    .map(x -> x * 10)
    .skip(1)
    .limit(2)
    .peek(System.out::println)
    .reduce(0, Integer::sum)
Enter fullscreen mode Exit fullscreen mode

Don't need no math right now, because what I wanted to show you is how this works step by step.

So, first of all, what the f- is a STREAM?

Things you have to have in mind is:

  • Streams are not a data structure
  • Streams doesn't store or modify data
  • It can be defined as a wrapper around a data source
  • Supports functional-style operations
  • Fast and convenient bulk processing over a data source
  • Iteration over elements are sequential and declarative
  • Parallel-friendly, immutable and thread-safe
  • Lazy evaluation
  • Single use
  • Don't confuse with Java I/O (ex. FileInputStream)

Besides that, we have:

  • Intermediate operations: stream chained operations that return new streams (pipeline)
  • Terminal operations: execute the stream and produces a non-stream object (result)

You can create a stream by invoking the methods stream() or parallelStream(), or even use Stream.of, Stream.ofNullable or Stream.iterate (like I did for Fibonacci).

So, let's try to get back a little bit and "debug" the easy example up there:

Arrays.asList(1, 2, 2, 3, 4, 4, 4, 5, 6, 8)
Enter fullscreen mode Exit fullscreen mode

Creates a new Collection, so we can iterate through its elements via stream

.stream()
Enter fullscreen mode Exit fullscreen mode

Starts a new stream

.distinct()
Enter fullscreen mode Exit fullscreen mode

Retain distinct elements, returning: [1, 2, 3, 4, 5, 6, 8]

.filter(x -> x % 2 == 0)
Enter fullscreen mode Exit fullscreen mode

Filter pair elements by applying mod 2, returning: [2, 4, 6, 8]

.map(x -> x * 10)
Enter fullscreen mode Exit fullscreen mode

Multiplicates each filtered element by 10, returning: [20, 40, 60, 80]

.skip(1)
Enter fullscreen mode Exit fullscreen mode

Skips the first element of the sequence, returning: [40, 60, 80]

.limit(2)
Enter fullscreen mode Exit fullscreen mode

Returns the first 2 elements of the sequence: [40, 60]
This is a "short-circuit" command, i.e., it'll interrupt the stream processing when the condition is match - in this case, when we reach the second element

.peek(System.out::println)
Enter fullscreen mode Exit fullscreen mode

Some kinda "debug" mode of stream, in this case it'll println each remaining element of the stream operation: 40 and 60

.reduce(0, Integer::sum)
Enter fullscreen mode Exit fullscreen mode

In this case, we're gonna effectively consume the stream and get a Integer result from it: the sum of all elements, where 0 is the starting value, and Integer::sum invokes the Integer.sum(int a, int b) function from the Integer class, that will be performed over each remaining element from the stream operation, that is, 40 and 60, then resulting 100.

Now, what about the Fibonacci stuff up there, man?
Right on, let's get back to it.

Stream.iterate
Enter fullscreen mode Exit fullscreen mode

By definition, we have:

Returns an infinite sequential ordered Stream produced by iterative application of a function f to an initial element seed, producing a Stream consisting of seed, f(seed), f(f(seed)), etc.

So that have said, all we have to do is to provide a seed (i.e., the starting point, or, the initial element) and then a function to be applied to the previous element to produce a new element.

new long[]{ 0L, 1L } // as the 1st parameter
Enter fullscreen mode Exit fullscreen mode

For the Fibonacci "kickstart" we need two elements: 0 and 1. Then, the sum of both will provide the next element and so on (you know that, right?). That said, this array will represent a pair of the first values of the Fibonacci sequence, as the first element of the stream.

p->new long[]{ p[1], p[0]+p[1] }) // as the 2nd parameter
Enter fullscreen mode Exit fullscreen mode

So, this object p will create a new array containing two elements:

  • p[1]: which is the previous array in the position 1;
  • p[0] + p[1]: which is the previous array in the position 0 plus its position 1;
.map(p -> p[0])
Enter fullscreen mode Exit fullscreen mode

The thing is, we have a stream of pair elements going on until now, however, our main goal is to print elements of this sequence, right? So now, to get the "current" element of this operation, by "mapping" the current pair of values of this stream to a single element, in this case, the array in position 0.

.limit(10)
Enter fullscreen mode Exit fullscreen mode

This is important, as said before, this will Return an infinite sequential ordered Stream by default. So, in order not to have this running forever, with your PC burning out, we define a "limit" to this operation, by processing the first 10 elements only.

.map(p->p+" ")
Enter fullscreen mode Exit fullscreen mode

This is silly, as we want to just print the sequence, it would be nice if we have a separator for each element, so here it is.

.forEach(System.out::print)
Enter fullscreen mode Exit fullscreen mode

The forEach is a terminal operation as well, and what it'll do is to iterate over the resulting collection of the Stream. So, here we're just printing the current element by using the well-known System.out.print.

If you're not familiar with expressions like this System.out::print yet, better check out about method reference, which is a special type of lambda expression, very useful to reduce boilerplate and improve readability. Maybe I'll cover more of this later by writing about Java functional programming, so stay tuned!

Discussion (0)