Are the map()
and filter()
of Optional
are lazy like Stream
?
How can I confirm their type?
There is a fundamental difference between a Stream
and an Optional
.
A Stream
encapsulates an entire processing pipeline, gathering all operations before doing anything. This allows the implementation to pick up different processing strategies, depending on what result is actually requested. This also allows to insert modifiers like unordered()
or parallel()
into the chain, as at this point, nothing has been done so far, so we can alter the behavior of the subsequent actual processing.
An extreme example is Stream.of(1, 2, 3).map(function).count()
, which will not process function
at all in Java 9, as the invariant result of 3
can be determined without.
In contrast, an Optional
is just a wrapper around a value (if not empty). Each operation will be performed immediately, to return either, a new Optional
encapsulating the new value or an empty Optional
. In Java 8, all methods returning an Optional
, i.e.map
, flatMap
or filter
, will just return an empty optional when being applied to an empty optional, so when chaining them, the empty optional becomes a kind of dead-end.
But Java 9 will introduce Optional<T> or(Supplier<? extends Optional<? extends T>>)
, which may return a non-empty optional from the supplier when being applied to an empty optional.
Since an Optional
represents a (possibly absent) value, rather than a processing pipeline, you can query the same Optional
as many times you want, whether the query returns a new Optional
or a final value.
It’s easy to verify. The following code
Optional<String> first=Optional.of("abc");
Optional<String> second=first.map(s -> {
System.out.println("Running map");
return s + "def";
});
System.out.println("starting queries");
System.out.println("first: "+(first.isPresent()? "has value": "is empty"));
System.out.println("second: "+(second.isPresent()? "has value": "is empty"));
second.map("second's value: "::concat).ifPresent(System.out::println);
will print
Running map
starting queries
first: has value
second: has value
second's value: abcdef
demonstrating that the mapping function is evaluated immediately, before any other query, and that we still can query the first
optional after we created a second via map
and query optionals multiple times.
In fact, it is strongly recommended to check via isPresent()
first, before calling get()
.
There is no equivalent stream code, as re-using Stream
instances this way is invalid. But we can show that intermediate operations are not performed before the terminal operation has been commenced:
Stream<String> stream=Stream.of("abc").map(s -> {
System.out.println("Running map");
return s + "def";
});
System.out.println("starting query");
Optional<String> result = stream.findAny();
System.out.println("result "+(result.isPresent()? "has value": "is empty"));
result.map("result value: "::concat).ifPresent(System.out::println);
will print
starting query
Running map
result has value
result value: abcdef
showing that the mapping function is not evaluated before the terminal operation findAny()
starts. Since we can’t query a stream multiple times, findAny()
even uses Optional
as return value, which allows us to do that with the final result.
There are other semantic differences between the operations of the same name, e.g. Optional.map
will return an empty Optional
if the mapping function evaluates to null
. For a stream, it makes no difference whether the function passed to map
returns null
or a non-null
value (that’s why we can count the elements without knowing whether it does).