Ce que j'essaie de faire est de filtrer la liste, puis de la mapper et d'utiliser orElse
si null
, puis de la récupérer dans la liste. Maintenant, je peux y arriver de cette façon:
return users.stream()
.filter(user -> id.equals(user.getId()))
.map(
user -> {
if(user.getData() != null) {
return user.getData();
}
return Collections.emptyMap();
}
)
.collect(Collectors.toList());
Mais la question est: comment puis-je améliorer cette structure et pourquoi ne puis-je pas utiliser orElse
dans ce cas?
Il pourrait être plus lisible avec l'opérateur conditionnel ternaire:
return users.stream()
.filter(user -> id.equals(user.getId()))
.map(
user -> (user.getData() != null)
? user.getData()
: emptyMap()
)
.collect(Collectors.toList())
;
Pour utiliser orElse
, vous devez créer une Optional
qui enveloppe user.getData()
. Je ne suis pas sûr que ce soit une bonne idée.
Si vous insistez pour utiliser orElse
(ou même mieux, orElseGet
, pour éviter d'évaluer emptyMap()
quand ce n'est pas nécessaire), cela peut ressembler à ceci:
return users.stream()
.filter(user -> id.equals(user.getId()))
.map(
user -> Optional.ofNullable(
user.getData()
).orElseGet(
() -> emptyMap()
)
)
.collect(Collectors.toList())
;
Comme je l'ai également souligné dans les commentaires et je doute fort que vous cherchiez peut-être les éléments suivants
users
.stream()
.filter(
user -> id.equals(user.getId())
&& (user.getData() != null)
)
.map(User::getData)
.collect(Collectors.toList())
;
Mais alors la question n’est pas assez claire pour dire quel est le type de retour éventuel de votre déclaration ou quelle est la emptyMap
utilisée dans votre code! Par conséquent, je doute fortement que vous ayez besoin d’une API Optional
en premier lieu pour cette opération.
Remarque: La solution indiquée ci-dessus suppose que emptyMap
est Collections.emptyMap
, ce que je ne sais pas pourquoi vouloir collecter dans une structure de données notée List<Map<K,V>>
.
Comment puis-je améliorer cette structure
Méthode 1:
return users.stream()
.filter(user -> id.equals(user.getId()))
.map(
user -> (user.getData() != null)
? user.getData()
: emptyMap()
)
.collect(Collectors.toList())
;
Méthode 2:
Faites en sorte que votre getData
retourne une Optional
: user -> user.getData().orElse(emptyMap())
Méthode 3:
Comme @Eran a déclaré: Optional.ofNullable
, utilisez ensuite orElse(emptyMap())
comme ci-dessus: user -> Optional.ofNullable(user.getData()).orElse(emptyMap())
Pourquoi je ne peux pas utiliser orElse dans ce cas?
Vous ne savez pas ce que vous voulez dire orElse
Si user.getData()
renvoie null
, il doit être encapsulé dans une Optional
pour appeler orElse
.
La findAny().orElse
du flux agit sur le résultat du flux lui-même. Mais ce dont vous avez besoin ici, c’est de vérifier si user.getData()
existe. Vous ne pouvez donc pas utiliser directement la variable orElse
du résultat de stream.
Objects::requireNonNullElse
!Je conseillerais deux choses pour rendre le code plus lisible. Cependant, je ne voudrais pas introduire artificiellement une Optional
.
Objects::requireNonNullElse
dans une méthode séparéeList<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());
}
Ici, vous utiliseriez Objects::requireNonNullElse
, qui renvoie l'objet passé dans le premier paramètre s'il ne s'agit pas de null
, et l'objet passé en tant que second paramètre si le premier paramètre est null
. L'utilisation d'une méthode distincte permet de transmettre une référence de méthode à Stream::map
, mais vous devez d'abord mapper les instances User
sur leurs données Map
.
Objects::requireNonNullElse
en ligneList<Map<?, ?> bar() {
//...
return users.stream()
.filter(user -> id.equals(user.getId()))
.map(User::getData)
.map(map -> Objects.requireNonNullElse(map, Collections.emptyMap()))
.collect(Collectors.toList());
}
Si vous ne souhaitez pas qu'une méthode distincte effectue uniquement cette tâche, vous pouvez la mettre en ligne et éventuellement même supprimer le premier mappage en faveur de .map(user -> Objects.requireNonNullElse(user.getData(), Collections.emptyMap()))
, mais je vous déconseille cela. N'ayez pas peur d'avoir plusieurs appels à Stream::map
si cela rend le code plus lisible.
Je voudrais préférer la première option} car cela rend le code très lisible: vous savez que vous mappez les instances User
aux données, puis vous rendez ces données nulles et sûres.
La deuxième option est correcte, mais souffre d’une très longue ligne qui pourrait prêter à confusion au premier abord. C'est beaucoup mieux que d'avoir un lambda multiligne. J'éviterais à tout prix les lambdas multilignes et en extrairais le contenu dans une méthode séparée.
Une chose que vous pourriez être en mesure d’améliorer est le nom de la méthode nullSafeMap
, afin d’éviter toute confusion entre Stream::map
et Java.util.Map
.
Notez que vous n'avez pas besoin d'utiliser Objects::requireNonNullElseGet
puisque Collections::emptyMap
est une méthode légère qui lance et renvoie uniquement une constante:
public static final <K,V> Map<K,V> emptyMap() {
return (Map<K,V>) EMPTY_MAP;
}
Objects::requireNonNullElseGet
est créé pour les objets par défaut dont l'extraction ou la création est lourde.
Si vous avez déjà Apache Collections 4 en tant que dépendance:
return users
.stream()
.filter(user -> id.equals(user.getId()))
.map(User::getData)
.map(MapUtils::emptyIfNull)
.collect(Collectors.toList())
;
Si vous n'utilisez pas les collections Apache, définissez simplement une méthode d'assistance:
public static <K,V> Map<K,V> emptyIfNull(Map<K,V> map) {
return map == null ? Collections.<K,V>emptyMap() : map;
}