Avoid using Optional to replace Null-checks
Optional wasn't designed to perform null-checks, and there's nothing wrong implicit null-check to begin with. Yes, if there are plenty of null-checks the code can become unreadable very quickly, but it's rather a design issue than a problem with the tools offered by the language.
The best remedy for the code that suffers from null-checks is to reduce the number of nullable fields, in the first place. In some cases it's fairly easy to achieve, especially if we are talking about Collections of Arrays like in this case, simply assign an empty Collection by default. This suggestion can be found in many places, for instance, see item "Return empty collections or arrays, not nulls" of the classic book "Effective Java", by Joshua Bloch.
Regarding the usage Optional.ofNullable()
here's quote from the answer by Stuart Marks, Java and OpenJDK developer:
The primary use of Optional is as follows:
Optional
is intended to provide a limited mechanism for library method
return types where there is a clear need to represent "no result," and
where using null for that is overwhelmingly likely to cause errors.
A typical code smell is, instead of the code using method chaining to
handle an Optional
returned from some method, it creates an Optional
from something that's nullable, in order to chain methods and avoid
conditionals.
With that being said, if you would follow the advice to avoid keeping a nullable Collection and quite using Optional
for null-checks the code can be written in a very simple well-readable manner.
Assuming your domain classes look like that (Lombok's @Getter
annotation is used for conciseness):
@Getter
public static class ProductSubList {
private LocalDateTime productDate;
}
@Getter
public static class MainProducts {
private List<ProductSubList> productSubList = new ArrayList<>(); // or Collection.emptyList() if the class is mean only to carry the data and doesn't expose methods to modify the list
}
Which would give the following code:
if (mainProducts == null) return Collections.emptyList();
return mainProducts.getProductSubList().stream()
.sorted(Comparator.comparing(ProductSubList::getProductDate))
.toList();
Pretty much self-explanatory. That would be the best option if can modify the classes you're using.
If for some reason, you can't modify MainProducts
and/or you've provided a simplified version and there are much more nullable moving parts, here a few more options.
Stream.ofNullable()
You can make use of the Java 9 Stream.ofNullable()
:
List<ProductSubList> productSubList = Stream.ofNullable(mainProducts)
.flatMap(mainProd -> Stream.ofNullable(mainProd.getProductSubList()))
.flatMap(Collection::stream)
.sorted(Comparator.comparing(ProductSubList::getProductDate))
.toList();
Note it's not a silver bullet, and I'm not saying to "plug Stream.ofNullable()
everywhere". Excessive usage Stream.ofNullable()
might create confusion because in it might seem there more levels of nesting than it's really is.
Stream.mapMulti()
We can reduce the number of stream operations and rearrange the code in a more readable way with Java 16 Stream.mapMulti()
:
List<ProductSubList> productSubList1 = Stream.ofNullable(mainProducts)
.<ProductSubList>mapMulti((mainProd, consumer) -> {
List<ProductSubList> prodSubLists = mainProd.getProductSubList();
if (prodSubLists != null) prodSubLists.forEach(consumer);
})
.sorted(Comparator.comparing(ProductSubList::getProductDate))
.toList();
Personally, I think the version above is good enough, but if you're unhappy with null-checks in the stream and want to see fancy one-liners, here's how it can be rewritten:
List<ProductSubList> productSubList2 = Stream.ofNullable(mainProducts)
.<ProductSubList>mapMulti((mainProd, consumer) ->
Objects.requireNonNullElse(
mainProd.getProductSubList(), List.<ProductSubList>of()
).forEach(consumer)
)
.sorted(Comparator.comparing(ProductSubList::getProductDate))
.toList();
Also see: