Как заменить несколько операторов If на Stream API

Как можно стримами заменить конструкцию из нескольких if/else-if?

if (!StringUtils.isEmpty(request.name())) {
    return userRepository.findByName(request.name());
} else if (!StringUtils.isEmpty(request.lastname())) {
    return userRepository.findByLastname(request.lastname());
} else if (!StringUtils.isEmpty(request.patronymic())) {
    return userRepository.findByPatronymic(request.patronymic());
} else if (!StringUtils.isEmpty(request.email())) {
    return userRepository.findByEmail(request.email());
}

Ответы (2 шт):

Автор решения: talex moved to Codidact

Можно попробовать так:

Optional.ofNullable(name)
    .map(userRepository::findByName)
    .or(() ->
        Optional.ofNullable(lastName)
            .map(userRepository::findByLastName)
    )
    .or(() ->
        Optional.ofNullable(patronimic)
            .map(userRepository::findByPatronimic)
    ).orElse(null);

Но по мне это не лучше.

→ Ссылка
Автор решения: Nowhere Man

Предыдущее решение от @talex скорее заменяет множественные if при помощи Optional::or (Java 9+).

Решение с использованием стримов может выглядеть так:

  • определяем некую мапу с ключами-геттерами Function<MyRequest, String> и значениями-ссылками на методы репозитория Function<String, List<UserEntity>>
  • берем стрим по элементам мапы, ищем первый подходящий элемент (геттер, возвращающий непустое значение), и для него вызываем соответствующий метод репозитория:
private UserRepository userRepository = new UserRepositoryImpl(); // реализация репозитория

Map<Function<MyRequest, String>, Function<String, List<UserEntity>>> map = new LinkedHashMap<>();
{
    map.put(MyRequest::name, userRepository::findByName);
    map.put(MyRequest::lastname, userRepository::findByLastname);
    map.put(MyRequest::patronymic, userRepository::findByPatronymic);
    map.put(MyRequest::email, userRepository::findByEmail);
}

public List<UserEntity> getUsers(MyRequest request) {
    return map.entrySet().stream()
        .filter(e -> !StringUtilities.isEmpty(e.getKey().apply(request)))
        .findFirst() // Optional<Map.Entry<F1, F2>>
        .map(e -> e.getValue().apply(e.getKey().apply(request)))
        .orElseGet(Collections::emptyList);
}

Онлайн демо


Обновление:
Аналогично можно определить статическую мапу, тогда потребуется использовать BiFunction для ссылки на метод репозитория UserRepository::findByXxx, а при вызове apply надо будет также передать экземпляр репозитория как показано ниже:

private static final Map<Function<MyRequest, String>, BiFunction<UserRepository, String, List<UserEntity>>> map = new LinkedHashMap<>();

static {
    // ссылки на метод интерфейса UserRepository
    map.put(MyRequest::name, UserRepository::findByName);
    map.put(MyRequest::lastname, UserRepository::findByLastname);
    map.put(MyRequest::patronymic, UserRepository::findByPatronymic);
    map.put(MyRequest::email, UserRepository::findListByEmail);
}

public List<UserEntity> getUsers(MyRequest request) {
    return map.entrySet().stream()
        .filter(e -> notEmpty(e.getKey().apply(request)))
        .findFirst() // Optional<Map.Entry<F1, BiF>>
        .map(e -> e.getValue().apply(
            userRepository,  // передаём *экземпляр* репозитория 
            e.getKey().apply(request)
        ))
        .orElseGet(Collections::emptyList);
}


Если окажется, что не все методы репозитория возвращают одинаковый тип, например, емайл считается уникальным и соответственно метод репозитория возвращает Optional, потребуется дописать метод-конвертор, чтобы получить более общую коллекцию:

interface UserRepository {
    // ...
    Optional<UserEntity> findByEmail(String value);

    default List<UserEntity> findList(Optional<UserEntity> opt) {
        return opt.map(Collections::singletonList)
            .orElseGet(Collections::emptyList);
    }
    
    default List<UserEntity> findListByEmail(String email) {
        return this.findList(findByEmail(email));
    }
}

Тогда при инициализации мапы соответственно придётся использовать корректную ссылку на метод userRepository::findListByEmail.

→ Ссылка