16

I am new to functional programming in java, and wonder how I should code to avoid NPE in (for example) this operation:

myList.stream()
      .reduce((prev, curr) -> prev.getTimestamp().isAfter(curr.getTimestamp()) ? prev : curr);
      .get().getTimestamp();

My intent here is to find the timestamp of the newest object in the list. Suggestions on how to better collect the last element are very welcome, but my main question here is actually why this works.

The documentation says that the function throws a NullPointerException "if the result of the reduction is null":

http://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html#reduce-java.util.function.BinaryOperator-

That is OK, but what I don't quite understand is why I don't get a NullPointerException when this code is run with a list containing only one element. I expected prev to be null in such a case. I tried debugging, but it just seems to step over the entire lambda expression when there is only one element.

Eran
  • 387,369
  • 54
  • 702
  • 768
aweibell
  • 405
  • 1
  • 5
  • 14

3 Answers3

26

As the JavaDoc of reduce says, reduce is equivalent to :

 boolean foundAny = false;
 T result = null;
 for (T element : this stream) {
     if (!foundAny) {
         foundAny = true;
         result = element;
     }
     else
         result = accumulator.apply(result, element);
 }
 return foundAny ? Optional.of(result) : Optional.empty();

Therefore, if the Stream has a single element, there is only one iteration of the loop, and the single element found in that iteration is returned.

The BinaryOperator would only be applied if the Stream has at least two elements.

Eran
  • 387,369
  • 54
  • 702
  • 768
9

Better:

  Optional<Thingie> max = 
      myList.stream()
            .reduce(BinaryOperator.maxBy(comparing(Thingie::getTimeStamp));

This overload of reduce() returns an Optional; blindly unpacking it with get is dangerous, and risks throwing NSEE. Unpack it with one of the safe operators like orElse or orElseThrow.

If there's a chance there are nulls in your stream, filter it first with .filter(t -> t != null).

Brian Goetz
  • 90,105
  • 23
  • 150
  • 161
  • Thanks. I already check that the stream is not empty, and don't think there could be null elements, but I guess your approach will always be better. I'll look at it tomorrow. – aweibell Nov 27 '14 at 23:06
  • That is elegant. – ahoffer Mar 02 '22 at 18:39
  • 1
    I think this guy should write a book about java or at least contribute to java source code – ACV Mar 26 '23 at 01:10
3

NPE will be thrown if the reduce operation actually results in a null value. For example if you try to do stream.reduce( (a,b) -> null ).

On another note, to find the newest timestamp, you can do this:

myList.stream()
    .map(t -> t.getTimeStamp())
    .max(Comparator.naturalOrder()
    .get();  // will throw an exception if stream is empty

Or, with static import and assuming your class is called Thing:

myList.stream().map(Thing::getTimeStamp).max(naturalOrder()).get();
Misha
  • 27,433
  • 6
  • 62
  • 78