list.stream().map(Foo::getAttr)
... returns a stream with one element, with a value of null.
The JavaDoc for findAny()
(and findFirst()
) says:
Returns:
an Optional describing some element of this stream, or an
empty Optional if the stream is empty
Throws:
NullPointerException - if the element selected is null
So findAny()
is doing exactly as documented: it's selecting a null, and as a result, throwing NullPointerException
.
This makes sense because Optional
is (again according to JavaDoc, but emphasis mine):
A container object which may or may not contain a non-null value
... which means you can guarantee that Optional.ifPresent( x -> x.method())
will never throw NullPointerException
due to x
being null.
So findAny()
could not return Optional.of(null)
. And Optional.empty()
means that the stream was empty, not that it found a null.
Many parts of the Stream
/Optional
infrastructure are about discouraging the use of nulls.
You can get around this by mapping the nulls to Optionals
, to yield an Optional<Optional<Foo>>
-- which looks a bit convoluted, but is an accurate representation of your domain. Optional.empty()
means the stream was empty. Optional.of(Optional.empty())
means it found one null element:
list.stream().map(Foo::getAttr).map(Optional::ofNullable).findAny()