What is the difference between using Stream.ofNullable() and stream()?

144 views Asked by At

While working with Streams in Java 11, I noticed the following situation. I'm trying to use the Stream API in two different ways on a non-null list.

First way:

Collection<Object> categoryIds = List.of(1L, 2L, 3L, 4L);

List<String> nullable = Stream.ofNullable(categoryIds)
        .map(Object::toString)
        .collect(Collectors.toList());

Second way:

Collection<Object> categoryIds = List.of(1L, 2L, 3L, 4L);

List<String> nullable = categoryIds.stream()
        .map(Object::toString)
        .collect(Collectors.toList());

The two ways I tried lead to two different results. The resulting list of the first way is a string containing a single element. This string is: "[1, 2, 3, 4]" The second way gives the desired and expected results. Returns a result list with 4 elements: "1", "2", "3", "4".

Is this behavior expected? Or is it a problem?

3

There are 3 answers

0
Ryuzaki L On BEST ANSWER

Stream.ofNullable you are creating a Stream object with single element or empty Stream for null object

Returns a sequential Stream containing a single element, , if non-null, otherwise returns an empty Stream.

If you want to get the same output using Stream.ofNullable you need to flatten the list using flatMap

List<String> nullable = Stream.ofNullable(categoryIds)
    .flatMap(List::stream)
    .map(Object::toString)
    .collect(Collectors.toList());

Collection.stream - it creates the stream object with collection elements in sequential order

Returns a sequential Stream with this collection as its source.

And the Collection.stream is equivalent to stream.of(T... values) method

 List<String> nullable = Stream.of(1L, 2L, 3L, 4L) // or categoryIds.stream()
    .map(Object::toString)
    .collect(Collectors.toList());
2
Reveson On

Stream.ofNullable(obj) is just equivalent of obj == null ? Stream.empty() : Stream.of(obj).

So yes, it is expected behaviour. what happened is you created a stream from one element which was a list (So you had a Stream<List<Integer>> instead of Stream<Integer>). Next, you just stringified a whole list (having one-element Stream<String>) and ended up with a one-element string list after .collect().

So if you want to have a stream of elements from the list, you need to flatten it first. For example like this:

Stream
   .ofNullable(categoryIds)
   .flatMap(Collection::stream)
   .map(Object::toString)
   .collect(Collectors.toList())
   ;

On the other hand, if you used a Stream.of(categoryIds) or categoryIds.stream() with a null categoryIds object, you would end up with NullPointerException and thats where Stream.ofNullable comes in handy.

0
dani-vta On

The reason why Stream.ofNullable(categoryIds) and categoryIds.stream() behave differently is because Stream.ofNullable(T t) expects a single object that will be treated as an element of the stream, in your case the collection categoryIds (see javadoc). Even though this collection contains several elements, the method won't stream them, instead, it will create a stream of Collection<Object> where the only element is categoryIds. This also explains why your first stream returns a single String element, because the only element in your stream (categoryIds) is mapped to the value returned from the invocation of its toString() method.

On the other hand, the stream() method of the Collection interface has a very different meaning (see javadoc), as the Collection is treated as the source of the stream. This means that the stream will iterate through the elements of categoryIds, and not treat the Collection as an element of the stream.

Returns a sequential Stream with this collection as its source.

The difference appears even more obvious when you notice how the role of categoryIds changes in the two writings. In fact, in the first case categoryIds is passed as a parameter to the stream, therefore you're creating a stream of collections. While in the second case, the method is invoked from categoryIds, creating a stream with its elements.

Finally, Stream.ofNullable is to be used in those instances where we need to create a stream from a single element, but we cannot admit null values (or better value) in our stream. It is simply a check that guarantees non null elements. Conversely, The method stream() allows null elements too.

As others have already mentioned, if you have a collection as an element of your stream, and you want to replace it with its elements, you need to invoke the flatMap operation.

Collection<Object> categoryIds = List.of(1L, 2L, 3L, 4L);

List<String> nullable = Stream.ofNullable(categoryIds)
     .flatMap(Collection::stream)
     .map(Object::toString)
     .collect(Collectors.toList());