So in this case it looks as if max()
is a proxy for Math.max
on the instance of this class. However there are no arguments passed to max, so does java 8 compile this to something like (Pseudo code):
@Override
public final OptionalInt max(Integer a, Integer b) {
//If neither a or b are null
return new OptionalInt.of(Math.max(a,b));
//Otherwise return empty because we can't compare the numbers
return OptionalInt.empty()
}
Not quite :). Let's start by figuring out what the reduce
operator actually does. The documentation explains that it performs a reduction on a sequence of numbers by applying an algorithm that is logically equivalent to the following:
public OptionalInt reduce(IntBinaryOperator op) {
boolean foundAny = false;
int result = 0;
for (int element : [this stream]) {
if (!foundAny) {
foundAny = true;
result = element;
}
else {
result = op.applyAsInt(result, element);
}
}
return foundAny ? OptionalInt.of(result)
: OptionalInt.empty();
}
Seems simple enough. If you can tell it how to take two numbers and 'reduce' or 'combine' them into one, then reduce
knows how to extend that logic to reduce an entire sequence into a single number. It handles the edge cases and the aggregation for you. All it needs from you is a function that takes in two numbers and gives it one back. That function should conform to the functional interface IntBinaryOperator
.
A functional interface is an interface that is meant to describe a single function. Specifically, it describes the argument types and the return type. The rest is largely superfluous. The signature for an IntBinaryOperator
looks like this:
int applyAsInt(int left, int right);
You can provide a function that conforms to this specification in several ways. Prior to Java 8, you might have done something like this:
stream.reduce(
new IntBinaryOperator() {
public int applyAsInt(int a, int b) {
return b > a ? b : a;
}
}
);
Java 8 gives us a shorthand form for functional interfaces called lambda expressions. These are a bit more concise, and while they are conceptually similar to anonymous inner classes, they're not quite the same thing.
stream.reduce((a, b) -> b > a ? b : a);
Both functions above are equivalent: they take in two numbers and return the larger of the two. As it turns out, every standard programming library has a function that does exactly the same thing. In Java, that function is Math.max
. So rather than writing this logic myself, I can delegate to Math.max
:
stream.reduce((a, b) -> Math.max(a, b));
But wait! All reduce
wants is a function that takes two numbers and returns one. Math.max
does that, so do I even need to wrap it in a lambda? It turns out I don't; I can tell reduce
to just call Math.max
directly:
stream.reduce(Math::max);
This says "I know you want a function, so I'm show you by name where to find one that's already been written". The compiler knows that Math.max
conforms to the (int, int) -> int
specification we need, so it emits some bytecode telling the VM how to 'bootstrap' it once it's needed. When the JVM hits your call to reduce
, it calls a special method that generates a wrapper class implementing IntBinaryOperator
that delegates to Math.max
in its implementation of applyAsInt
. It only performs this 'bootstrapping' step once. Since calling Math.max
doesn't rely on anything other than the two numbers that get passed in, it can cache that implementation and use it the next time you wind up on this code path.