Home > Blockchain >  Java Optional - Sorting a Nullable List stored as a property of a Nullable object
Java Optional - Sorting a Nullable List stored as a property of a Nullable object

Time:11-09

How can I approach sorting with an Optional?

Both mainProducts or productSublist in the code below maybe null. The resulting list productSubList should be sorted by Date.

List<ProductSubList> productSubList = Optional.of(mainProducts)
        .map(MainProducts::getProductSubList)
        .ifPresent(list -> list.stream().sorted(Comparator.comparing(ProductSubList::getProductDate))
        .collect(Collectors.toList()));

The code shown above produces a compilation error:

Required type: ProductSubList ; Provided:void

Part Answer which seems too long, is there a way to make this shorter?

  if (Optional.of(mainProducts).map(MainProducts::getProductSubList).isPresent()) {
      productSublist = mainProducts.getProductSubList().stream()
              .sorted(Comparator.comparing(ProductSubList::getProductDate))
              .collect(Collectors.toList());
    }

Resource: How to avoid checking for null values in method chaining?

CodePudding user response:

Seems you're looking for something like this:

List<ProductSubList> productSubList = Optional.ofNullable(mainProducts)
        .map(MainProducts::getProductSubList)
        .map(list -> list.stream()
                .sorted(Comparator.comparing(ProductSubList::getProductDate))
                .collect(Collectors.toList()))
        .orElse(Collections.emptyList());

CodePudding user response:

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 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.

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();

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:

  • Related