The reason for this is the use of Optional<T> in the return. Optional is not allowed to contain null. Essentially, it offers no way of distinguishing situations "it's not there" and "it's there, but it is set to null".
That's why the documentation explicitly prohibits the situation when null is selected in findFirst():
Answer from Sergey Kalinichenko on Stack OverflowThrows:
NullPointerException- if the element selected isnull
The reason for this is the use of Optional<T> in the return. Optional is not allowed to contain null. Essentially, it offers no way of distinguishing situations "it's not there" and "it's there, but it is set to null".
That's why the documentation explicitly prohibits the situation when null is selected in findFirst():
Throws:
NullPointerException- if the element selected isnull
As already discussed, the API designers do not assume that the developer wants to treat null values and absent values the same way.
If you still want to do that, you may do it explicitly by applying the sequence
.map(Optional::ofNullable).findFirst().flatMap(Function.identity())
to the stream. The result will be an empty optional in both cases, if there is no first element or if the first element is null. So in your case, you may use
String firstString = strings.stream()
.map(Optional::ofNullable).findFirst().flatMap(Function.identity())
.orElse(null);
to get a null value if the first element is either absent or null.
If you want to distinguish between these cases, you may simply omit the flatMap step:
Optional<String> firstString = strings.stream()
.map(Optional::ofNullable).findFirst().orElse(null);
System.out.println(firstString==null? "no such element":
firstString.orElse("first element is null"));
This is not much different to your updated question. You just have to replace "no such element" with "StringWhenListIsEmpty" and "first element is null" with null. But if you don’t like conditionals, you can achieve it also like:
String firstString = strings.stream()
.map(Optional::ofNullable).findFirst()
.orElseGet(()->Optional.of("StringWhenListIsEmpty"))
.orElse(null);
Now, firstString will be null if an element exists but is null and it will be "StringWhenListIsEmpty" when no element exists.
Videos
Well, as for me, the best way is to use functional programing and continue to work with optional. So, for example if you need to pass this string to some service, you can do:
String fisrstString = myList.stream().findFirst().get();
service.doSomething(fisrstString);
But this looks not so good. Instead you can use the pros of functional programing, and do:
myList.stream().findFirst().ifPresent(service::doSomething);
You should make use of the Optional returned by findFirst() instead of trying to get its value (if it's actually present).
myList.stream()
.findFirst()
.ifPresent(/* consume the string here, if present */);
The Optional.ifPresent method receives a Consumer that will be used only if the Optional contains a non-null value.
The problem is that we Java developers are so used to the imperative paradigm... In particular, we are used to getting an object and pushing it i.e. to a method:
String myString = "hello"; // getting an object here
System.out.println(myString); // pushing the object to System.out here
// (via the println method)
With the Optional returned by Stream.findFirst() you were doing the same as above:
String myString = myList.stream()
.findFirst()
.get(); // getting a string here
System.out.println(myString); // pushing the string here
On the other hand, the functional paradigm (including Optional) usually works the other way:
myList.stream()
.findFirst()
.ifPresent(myString -> System.out.println(myString));
Here, you don't get the string and then push it to some method. Instead, you provide an argument to Optional's ifPresent operation and let the implementation of Optional push the value to your argument. In other words, you pull the value wrapped by the Optional by means of ifPresent's argument. ifPresent will then use this Consumer argument, only if the value is present.
This pull pattern is seen a lot in functional programming and is very useful, once you get used to it. It just requires us developers to start thinking (and programming) in a different way.