/ STREAMS, FUNCTIONAL PROGRAMMING, STATE, OBJECT-ORIENTED PROGRAMMING

Java streams and state

With Java 8 streams, it seems Functional Programming has won. Long live statelessness and recursion! Reality is a bit more nuanced: as always in software programming, it depends. I believe that the more tools in your toolbelt, the better it is.

When all you have is a hammer, everything looks like a nail.

In Functional Programming, every function needs to be pure: output only depends on input, and there are no side-effects. For this reason, Java methods to create infinite streams are not used (much?).

Take for example Stream.generate(). I’ve seen only two usages:

Stream.generate(Math::random);    (1)
Stream.generate(() -> "Java");    (2)
1Random value generation
2Constant value generation

Likewise, the following snippet is the most common example of Stream.iterate():

Stream.iterate(0, i -> i + 1);

Iteration is easy when the computation of the next term is simple. A data structure is required when the computation becomes more complex.

Here’s an example with a function that computes the square of the index, so that it can be summed afterwards (e.g. 12 + 22 + 32 + …​ n2):

Stream.iterate(new double[]{1, 1},
               pair -> new double[]{pair[0] + 1, Math.pow(pair[0] + 1, 2)});

I’m not sure this is the most readable code snippet. To make it cleaner, let’s create a dedicated structure:

public class Pair {

    public final int index;
    public final double value;

    public Pair(int index, double value) {
        this.index = index;
        this.value = value;
    }
}

Stream.iterate(new Pair(1, 1),
               pair -> new Pair(pair.index + 1, Math.pow(pair.index + 1, 2)));

This is only marginally better, as the computation logic is still "hidden" in the lambda. One solution could be to let the Pair compute the next value:

public class Pair {

    public static final Pair SEED = new Pair(1, 1);

    public final int index;
    public final double value;

    public Pair(int index, double value) {
        this.index = index;
        this.value = value;
    }

    public Pair next() {
        return new Pair(index + 1, Math.pow(index + 1, 2));
    }
}

Stream.iterate(Pair.SEED, Pair::next);

I think this is a pretty neat solution. It can be re-used for other functions/series/suites as well.

Here’s an example for the factorial function:

public class Factorial {

    public static final Factorial SEED = new Factorial(1, 1);

    public final int index;
    public final int value;

    public Factorial(int index, int value) {
        this.index = index;
        this.value = value;
    }

    public Factorial next() {
        return new Factorial(index + 1, value * index);
    }
}

Stream.iterate(Pair.SEED, Pair::next);

And another one for the Fibonacci suite:

public class Fibonacci {

    public static final Fibonacci SEED = new Fibonacci(1, 1);

    public final int previous;
    public final int value;


    public Fibonacci(int previous, int value) {
        this.previous = previous;
        this.value = value;
    }

    public Fibonacci next() {
        return new Fibonacci(value, value + previous);
    }
}

Stream.iterate(Fibonacci.SEED, Fibonacci::next);

Notice how state was introduced? It made the code easier to read.

Now, let’s push the things a bit further. Remember the Stream.generate() function above? With the correct stateful Supplier, it can replace Stream.iterate():

public class IncrementSupplier implements Supplier<Integer> {

    private int value;

    public IncrementSupplier(int seed) {                      (1)
        this.value = seed;
    }

    @Override
    public Integer get() {
        return ++value;                                       (2)
    }
}

Stream.iterate(0, i -> i + 1);                                (3)
Stream.generate(new IncrementSupplier(0));                    (3)
1The seed becomes part of the Supplier
2Increment the value and return it
3Those are equivalent

Developers are more creatures of passion, than creatures of reason. It’s not because Java offers Functional Programming, that it now disallows state. While state is an issue in concurrent programming contexts, it can be a huge help toward more readable code.

Nicolas Fränkel

Nicolas Fränkel

Nicolas Fränkel is a Developer Advocate with 15+ years experience consulting for many different customers, in a wide range of contexts (such as telecoms, banking, insurances, large retail and public sector). Usually working on Java/Java EE and Spring technologies, but with focused interests like Rich Internet Applications, Testing, CI/CD and DevOps. Currently working for Exoscale. Also double as a teacher in universities and higher education schools, a trainer and triples as a book author.

Read More