7

Given this code:

class Foo {
  Integer attr;
  public Integer getAttr() {return attr;}
}

List<Foo> list = new ArrayList<>();
list.add(new Foo());

list.stream().map(Foo::getAttr).findAny().orElse(null);  //A
list.stream().findAny().map(Foo::getAttr).orElse(null);  //B

Line A throws

java.lang.NullPointerException: null

while line B returns null.

What is the cause of this behaviour? Both findAny() and map() return Optional<T>.

Stuart Marks
  • 127,867
  • 37
  • 205
  • 259
jerzy.burzek
  • 71
  • 1
  • 2
  • 1
    See also [Why does findFirst() throw a NullPointerException if the first element it finds is null?](https://stackoverflow.com/q/32466799/2711488) – Holger Jul 07 '17 at 13:00

4 Answers4

7
list.stream().map(Foo::getAttr).findAny().orElse(null);

Java doc for streams says that Stream: "returns a stream consisting of the results of applying the given function to the elements of this stream", and findAny() "could return aNullPointerException - if the element selected is null". In your class Foo, Integer(not int) by default is set to null because is declared but not initialized. see Primitives see default values and Object initialization in Java

Initialization is different for: A) Class Members (Objects and primitives) B) Local Variables

Jacob G.
  • 28,856
  • 5
  • 62
  • 116
LuCG
  • 109
  • 9
  • You should edit your answer adding your source for that ;) I know them but this is better – AxelH Jul 07 '17 at 07:44
5

First, your two code snippet map are different operations:

//            v--- stream intermediate operation
list.stream().map(Foo::getAttr).findAny().orElse(null);  //A
//                      v---- a Optional utility method 
list.stream().findAny().map(Foo::getAttr).orElse(null);  //B

and the NullPointerException occurs in Stream#findAny operation, since it can't accept a null value. due to it uses Optional.of rather than Optional.ofNullable. and the documentation of Stream#findAny is already asserts:

Throws:

NullPointerException - if the element selected is null

so if you want your A code snippet works fine you must filter all null values before calling Stream#findAny, for example:

//when no elements in stream, `findAny` will be return a empty by Optional.empty()
//                                                       v   
list.stream().map(Foo::getAttr).filter(Objects::nonNull).findAny().orElse(null);//A
Community
  • 1
  • 1
holi-java
  • 29,655
  • 7
  • 72
  • 83
  • 1
    Well done! I thought nobody was going to talk about `filter(Objects::nonNull)` – fps Jul 07 '17 at 17:04
4

Well it's obviously because of the order in which you perform these operations and also because findAny explicitly says : throws NullPointerException if the element selected is null

When you do map(Foo::getAttr) you have effectively mapped that to null, so your Stream now contains a null; thus findAny breaks with an Exception (since findAny is applied on that null)

The other operation first finds the Foo object, then maps it to Foo::getAttr (thus mapping it to Optional.empty()), thus orElse is called.

Also, this would make more sense (for me at least):

 list.stream()
     .findAny()
     .flatMap(f -> Optional.ofNullable(f.getAttr()))
     .orElse(null);

flatMap would map to Optional<Integer> (attributes), in case this one is empty get the orElse result.

Eugene
  • 117,005
  • 15
  • 201
  • 306
  • 3
    Ahem, it is impossible to have an `Optional` mapping to `null`. An `Optional` is either, empty or mapping to a non-`null` value. The OP’s second case, calling `map(Foo::getAttr)` on an optional, does exactly the same as your `flatMap(f -> Optional.ofNullable(f.getAttr()))`. In either case, the result is an empty optional and the argument passed to `orElse` is returned. (and saying “`orElse` is not called” makes no sense anyway. There is no way how the invocation of `orElse` could be skipped) – Holger Jul 07 '17 at 12:57
  • @Holger gosh that was stupid from my side, thank you – Eugene Jul 09 '17 at 09:47
0
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()
slim
  • 40,215
  • 13
  • 94
  • 127
  • 3
    using a `flatMap` would make it cleaner: `list.stream().findAny().flatMap(f -> Optional.ofNullable(f.getAttr())).orElse(null);` like I wrote above... – Eugene Jul 07 '17 at 08:59
  • 1
    @Eugene If you want to get nulls out of it, yes. In my mind though, this code can be a boundary between a domain where nulls can exist, and a domain in which everything is guaranteed non-null. – slim Jul 07 '17 at 09:30