API потока Java 8 или использование EE

То, что я пытаюсь сделать это, чтобы отфильтровать список, а затем отобразить его и использовать orElse если null, а затем собрать его обратно к списку. Теперь я могу добиться этого следующим образом:

return users.stream()
    .filter(user -> id.equals(user.getId()))
    .map(
        user -> {
            if(user.getData() != null) {
                return user.getData();
            }
            return Collections.emptyMap();
        }
    )
    .collect(Collectors.toList());

Но вопрос в том, как я могу улучшить эту структуру и почему я не могу использовать orElse в этом случае?

Ответы

Ответ 1

Это может быть более понятным с помощью тернарного условного оператора:

return users.stream()
    .filter(user -> id.equals(user.getId()))
    .map(
        user -> (user.getData() != null) 
        ? user.getData() 
        : emptyMap()
    )
    .collect(Collectors.toList())
;

Для того, чтобы использовать orElse вам придется создать Optional, который оборачивает user.getData(). Я не уверен, что это хорошая идея.

Если вы настаиваете на том, чтобы использовать orElse (или даже лучше, orElseGet, чтобы избежать оценки emptyMap() когда это не требуется), он может выглядеть следующим образом:

return users.stream()
    .filter(user -> id.equals(user.getId()))
    .map(
        user -> Optional.ofNullable(
            user.getData()
        ).orElseGet(
            () -> emptyMap()
        )
    )
    .collect(Collectors.toList())
;

Ответ 2

Как я отметил в комментариях, и я очень сомневаюсь, что вы можете просто искать следующие

users
    .stream()
    .filter(
        user -> id.equals(user.getId()) 
        && (user.getData() != null)
    )
    .map(User::getData)
    .collect(Collectors.toList())
;

Но тогда вопрос не достаточно ясен, чтобы сказать, каков конечный тип возвращаемого выражения вашего оператора или что такое emptyMap используемая в вашем коде! Поэтому я очень сомневаюсь, что если вам понадобится Optional API на первом месте для этой операции.

Примечание. Вышеупомянутое решение предполагает, что emptyMap - это Collections.emptyMap который я не уверен, почему кто-то хочет собирать в структуре данных, которая обозначается как List<Map<K,V>>.

Ответ 3

Как я могу сделать эту структуру лучше

Способ 1:

return users.stream()
    .filter(user -> id.equals(user.getId()))
    .map(
        user -> (user.getData() != null)
        ? user.getData() 
        : emptyMap()
    )
    .collect(Collectors.toList())
;

Способ 2:

Сделайте свой getData возвратом Optional: user → user.getData().orElse(emptyMap())

Способ 3:

Как сказал @Eran: Optional.ofNullable затем используйте orElse(emptyMap()) как указано выше: user → Optional.ofNullable(user.getData()).orElse(emptyMap())

Почему я не могу использовать orElse в этом случае?

Не уверен, что orElse вы имеете в виду

  1. Если user.getData() возвращает значение null, он должен быть завернут в Optional для вызова orElse.

  2. Stream findAny().orElse работает findAny().orElse с результатом потока. Но здесь вам нужно проверить, существует ли user.getData(). Таким образом, вы не orElse напрямую использовать результат потока orElse.

Ответ 4

Используйте Objects::requireNonNullElse !

Я бы посоветовал две вещи, чтобы сделать код более читабельным. Я не стал бы, однако, искусственно вводить Optional.


Первый вариант: Objects::requireNonNullElse в отдельном методе

List<Map<?, ?> bar() {
    //...

    return users.stream()
                .filter(user -> id.equals(user.getId()))
                .map(User::getData)
                .map(Foo::nullSafeMap)
                .collect(Collectors.toList());
}

private static Map<?, ?> nullSafeMap(final Map<?, ?> map) {
    return Objects.requireNonNullElse(map, Collections.emptyMap());
}

Здесь вы должны использовать Objects::requireNonNullElse, который возвращает объект, переданный в первом параметре, если он не равен null, и объект, переданный в качестве второго параметра, если первый параметр равен null. Наличие отдельного метода позволяет передавать ссылку на метод в Stream::map, но требует, чтобы вы сначала сопоставили экземпляры User с их Map данных.


Второй вариант: встроенные Objects::requireNonNullElse

List<Map<?, ?> bar() {
    //...

    return users.stream()
                .filter(user -> id.equals(user.getId()))
                .map(User::getData)
                .map(map -> Objects.requireNonNullElse(map, Collections.emptyMap()))
                .collect(Collectors.toList());
}

Если вы не хотите, чтобы отдельный метод выполнял только эту единственную задачу, вы можете встроить метод и при необходимости даже удалить первое отображение в пользу .map(user → Objects.requireNonNullElse(user.getData(), Collections.emptyMap())), но я бы посоветовал против этого. Не бойтесь делать несколько вызовов Stream::map если это делает код более читабельным.


Заключение

Я бы предпочел первый вариант, так как он делает код очень читабельным: вы знаете, что сопоставляете экземпляры User с данными, а затем делаете эти данные абсолютно безопасными.

Второй вариант в порядке, но страдает от очень длинной строки, которая может сбить с толку на первый взгляд. Это намного лучше, чем многострочная лямбда. Я бы во что бы то ни стало избегал многострочных лямбд и всегда извлекал их содержимое в отдельный метод.

Одна вещь, которую вы могли бы улучшить, это имя метода nullSafeMap, чтобы избежать путаницы между Stream::map и java.util.Map.

Обратите внимание, что вам не нужно использовать Objects::requireNonNullElseGet так как Collections::emptyMap - это легкий метод, который только приводит и возвращает константу:

public static final <K,V> Map<K,V> emptyMap() {
    return (Map<K,V>) EMPTY_MAP;
}

Objects::requireNonNullElseGet создан для объектов по умолчанию, поиск или создание которых является тяжелым.

Ответ 5

Если у вас уже есть Apache Collections 4 в качестве зависимости:

return users
    .stream()
    .filter(user -> id.equals(user.getId()))
    .map(User::getData)
    .map(MapUtils::emptyIfNull)
    .collect(Collectors.toList())
;

Если вы не используете Apache Collections, просто определите вспомогательный метод:

public static <K,V> Map<K,V> emptyIfNull(Map<K,V> map) {
    return map == null ? Collections.<K,V>emptyMap() : map;
}