So, finally making a relatively large jump from Java 6 to Java 8, I've read up a fair amount of Java 8 Streams API. Unfortunately, almost all the examples that have been asked are almost close to what I'm trying to figure out how to do, but not close enough.
What I have is
final List<Function<? super Double, Double>> myList = generateList();
final double myVal = calculate(10);
private double calculate(double val) {
for (Function<? super Double, Double> function : this.myList) {
val += function.apply(val);
}
return val;
}
Now, I've come to understand I could do something similar with .stream().forEach()
, but that only applies the foreach and streams require final variables. I've tried to explore a bit with DoubleStream
to get a sum()
, but I'd need to re-apply the current sum to each Function
and add that sum to the next function, as the code example above shows.
Is this possible with pure Stream API?
Edit: So after testing with the reduce()
area, I ran a simple test on the time it takes to perform this type of calculation and the results aren't in favor of streams. Here's an example https://gist.github.com/gabizou/33f616c08bde5ab97e56. Included are the log outputs from the fairly basic test.
You can use the stream API to compose a function out of your list of functions.
The stream operation which produces the composed function does not have the associativity problem and could in theory even run in parallel (though there is no benefit here) while the result is a single function which is per se sequential and never dissolved into parts which would need to be associative.
Yes, you can use a stream solution, by performing a reduction:
A reduction takes each element and aggregates (reduces) it to one value. There are 3 flavours of the
reduce()
method - the one used here does the trick.Some test code:
Output:
This particular example is very problematic for the Java 8 streams. They are designed for operations in which the order is not important.
Function application is not associative. To explain, let's take a simpler example, in which one wants to take a number and divide it by a list of numbers:
So, what you get is
The arithmetic is simple - division is left-associative, meeaning that this is equivalent to
not
and not
The stream operations, those that are based on reduce/collectors, require that the "reducing" operation will be left-associative. This is because they want to allow the operation to be parallelized, such that some threads will do some of the operations, and then the result could be combined. Now, if your our operator was multiplication rather than division, this would not be a problem, because
is the same as
which means one thread could do the
a × 3.5 × 7.0
, and another could do the0.5 × 19.0
operation, and then you could multiply the result and get the same thing as in the sequential calculation. But for division, that doesn't work.Function application is also non-associative, just like division. That is, if you have functions
f
,g
andh
, and you run your sequential calculation, you'll end up with:Now, if you have two intermediate threads, one applying
f
andg
, the other one applyingh
, and you want to combine the result - there is no way to get the correct values into theh
in the first place.You may be tempted to try this with a method like
Stream.reduce
, as @Bohemian suggested. But the documentation warns you against this:For an operation like
+
, the identity is 0. For*
, the identity is 1. So it is against the documentation to use yourval
as theidentity
. And the second condition is even more problematic.Although the current implementation for a non-parallel stream does not use the combiner part, which makes both the conditions unneeded, this is not documented, and a future implementation, or a different JRE's implementation, may decide to create intermediate results and use the combiner to join them, perhaps to improve performance or for any other consideration.
So, despite the temptation, one should not use
Stream.reduce
to try to imitate the original sequential processing.There is a way to do it, that doesn't actually break the documentation. It involves keeping a mutable object that holds the result (it has to be an object so that it's effectively final while still mutable), and using the
Stream.forEachOrdered
, which guarantees that the operations will be performed in the order they appear in the stream, if the stream is ordered. And a list's stream has a defined order. This works even when you usemyList.stream().parallel()
.Personally, I find that your original loop is more readable than this, so there is really no advantage to using streams here.
Based on a comment by @Tagir Valeev, there is a foldLeft operation planned for future Java versions. It may look more elegant when that happens.