How do I avoid two filters when condition involves an Optional

Viewed 457

I have code like this:

myList.stream()
            .filter(item -> item.getName().isPresent())
            .filter(item -> item.getName().get().equalsIgnoreCase(otherName))
            .findFirst();

(... where item.getName() has a return type of Optional<String>)

How can I condense two filters into one here?

4 Answers

You can use Optional.filter():

myList.stream()
    .filter(item -> 
        item.getName().filter(n -> n.equalsIgnoreCase(otherName)).isPresent())
    .findFirst();

The inner filter call is Optional.filter, which returns an empty optional if the filter condition was not met.

You can avoid it with Optional::stream() method:

If a value is present, returns a sequential Stream containing only that value, otherwise returns an empty Stream.

myList.stream()
  .map(Item::getName)
  .flatMap(Optional::stream)
  .filter(otherName::equalsIgnoreCase)
  .findFirst();

I would solve it the "OOP" way. Make a method in Item 'isNameEqual' or something like that which would hide the logic with the optional and the ignore case.

Then you can have just 1 filter statement with Item::isNameEqual.

You don't want to have too much ceremony in the lambda.

Combine the filters using a logical AND (&&):

myList.stream()
    .filter(item -> item.getName().isPresent() 
        && item.getName().get().equalsIgnoreCase(otherName))
    .findFirst();

This is safe because if the value is not present, the && will short-circuit to false.

Or use the conditional operator:

myList.stream()
    .filter(item -> item.getName().isPresent()  
        ? item.getName().get().equalsIgnoreCase(otherName)
        : false)
    .findFirst();

Another way would be to use null as the value if the Optional<String> has no value and equalsIgnoreCase(null) will always return false:

myList.stream()
    .filter(item -> otherName.equalsIgnoreCase(item
        .getName()
        .orElseGet(null)))
    .findFirst();
Related