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
вы имеете в виду
-
Если user.getData()
возвращает значение null
, он должен быть завернут в Optional
для вызова orElse
.
-
Stream findAny().orElse
работает findAny().orElse
с результатом потока. Но здесь вам нужно проверить, существует ли user.getData()
. Таким образом, вы не orElse
напрямую использовать результат потока orElse
.
Ответ 4
Я бы посоветовал две вещи, чтобы сделать код более читабельным. Я не стал бы, однако, искусственно вводить 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;
}