Le Java 8 Collectors.toMap
lance un NullPointerException
si l'une des valeurs est 'null'. Je ne comprends pas ce comportement, les cartes peuvent contenir des pointeurs nuls comme valeur sans aucun problème. Y a-t-il une bonne raison pour laquelle les valeurs ne peuvent pas être nulles Collectors.toMap
?
De plus, y a-t-il une bonne façon de résoudre ce problème en Java 8, ou devrais-je revenir à la boucle ancienne pour la boucle?
Un exemple de mon problème:
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
class Answer {
private int id;
private Boolean answer;
Answer() {
}
Answer(int id, Boolean answer) {
this.id = id;
this.answer = answer;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public Boolean getAnswer() {
return answer;
}
public void setAnswer(Boolean answer) {
this.answer = answer;
}
}
public class Main {
public static void main(String[] args) {
List<Answer> answerList = new ArrayList<>();
answerList.add(new Answer(1, true));
answerList.add(new Answer(2, true));
answerList.add(new Answer(3, null));
Map<Integer, Boolean> answerMap =
answerList
.stream()
.collect(Collectors.toMap(Answer::getId, Answer::getAnswer));
}
}
Trace de la pile:
Exception in thread "main" java.lang.NullPointerException
at java.util.HashMap.merge(HashMap.java:1216)
at java.util.stream.Collectors.lambda$toMap$168(Collectors.java:1320)
at java.util.stream.Collectors$$Lambda$5/1528902577.accept(Unknown Source)
at java.util.stream.ReduceOps$3ReducingSink.accept(ReduceOps.java:169)
at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1359)
at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:512)
at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:502)
at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:499)
at Main.main(Main.java:48)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:483)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:134)
Ce problème existe toujours dans Java 11.
null
a toujours été un peu problématique, comme dans TreeMap. Peut-être un bon moment pour essayerOptional<Boolean>
? Sinon, divisez et utilisez le filtre.null
pourrait être un problème pour une clé, mais dans ce cas, c'est la valeur.null
,HashMap
par exemple, peuvent avoir unenull
clé et un nombre quelconque denull
valeurs, vous pouvez essayer de créer un personnalisé enCollector
utilisant unHashMap
au lieu d'utiliser celui par défaut.HashMap
- comme indiqué dans la première ligne de stacktrace. Le problème n'est pas qu'une valeurMap
ne peut pas contenirnull
, mais que le deuxième argument de laMap#merge
fonction ne peut pas être nul.Réponses:
Vous pouvez contourner ce bogue connu dans OpenJDK avec ceci:
Ce n'est pas très joli, mais ça marche. Résultat:
( ce tutoriel m'a le plus aidé.)
la source
() -> new TreeMap<>(String.CASE_INSENSITIVE_ORDER)
de créer uneString
clé insensible à la casseTreeMap
.Map<Integer, Boolean> collect = list.stream().collect(HashMap<Integer, Boolean>::new, (m,v)->m.put(v.getId(), v.getAnswer()), HashMap<Integer, Boolean>::putAll);
. J'ai eu:incompatible types: cannot infer type-variable(s) R (argument mismatch; invalid method reference no suitable method found for putAll(java.util.Map<java.lang.Integer,java.lang.Boolean>,java.util.Map<java.lang.Integer,java.lang.Boolean>) method java.util.Map.putAll(java.util.Map) is not applicable (actual and formal argument lists differ in length)
HashMap
, puis appelezputAll()
pour chaque entrée unique. Personnellement, dans des circonstances données, j'irais avec une solution non stream, ouforEach()
si l'entrée est parallèle.Ce n'est pas possible avec les méthodes statiques de
Collectors
. Le javadoc detoMap
explique quitoMap
est basé surMap.merge
:et le javadoc
Map.merge
dit:Vous pouvez éviter la boucle for en utilisant la
forEach
méthode de votre liste.mais ce n'est pas vraiment simple que l'ancienne:
la source
Map.merge
. Cette IMHO est une faille dans la mise en œuvre qui restreint un cas d'utilisation parfaitement acceptable qui a été ignoré. Les méthodes surchargées detoMap
do indiquent l'utilisation deMap.merge
mais pas celle que l'OP utilise.J'ai écrit un
Collector
qui, contrairement à celui par défaut de Java, ne plante pas lorsque vous avez desnull
valeurs:Remplacez simplement votre
Collectors.toMap()
appel par un appel à cette fonction et cela résoudra le problème.la source
null
valeurs et les utiliserputIfAbsent
ne fonctionnent pas bien ensemble. Il ne détecte pas les clés en double lors de leur mappage surnull
…Oui, une réponse tardive de ma part, mais je pense que cela peut aider à comprendre ce qui se passe sous le capot au cas où quelqu'un voudrait coder une autre
Collector
logique.J'ai essayé de résoudre le problème en codant une approche plus native et simple. Je pense que c'est aussi direct que possible:
Et les tests utilisant JUnit et assertj:
Et comment l'utilisez-vous? Eh bien, utilisez-le plutôt que
toMap()
comme le montrent les tests. Cela rend le code appelant aussi propre que possible.EDIT:
implémentation de l'idée de Holger ci-dessous, ajout d'une méthode de test
la source
(map1, map2) -> { int total = map1.size() + map2.size(); map1.putAll(map2); if(map1.size() < total.size()) throw new IllegalStateException("Duplicate key(s)"); return map1; }
accumulator()
que ça vérifie. Peut-être que je devrais faire quelques streams parallèles une fois :)Voici un collecteur un peu plus simple que celui proposé par @EmmanuelTouzery. Utilisez-le si vous le souhaitez:
Nous remplaçons simplement
null
par un objet personnalisénone
et faisons l'opération inverse dans le finisseur.la source
Si la valeur est une chaîne, cela peut fonctionner:
map.entrySet().stream().collect(Collectors.toMap(e -> e.getKey(), e -> Optional.ofNullable(e.getValue()).orElse("")))
la source
Selon le
Stacktrace
Quand s'appelle le
map.merge
Il fera un
null
chèque comme première choseJe n'utilise pas Java 8 si souvent, donc je ne sais pas s'il existe une meilleure façon de le réparer, mais le réparer est un peu difficile.
Vous pourriez faire:
Utilisez un filtre pour filtrer toutes les valeurs NULL, et dans le code Javascript, vérifiez si le serveur n'a envoyé aucune réponse pour cet identifiant signifie qu'il n'y a pas répondu.
Quelque chose comme ça:
Ou utilisez peek, qui est utilisé pour modifier l'élément de flux pour élément. En utilisant Peek, vous pouvez changer la réponse en quelque chose de plus acceptable pour la carte, mais cela signifie modifier un peu votre logique.
On dirait que si vous souhaitez conserver la conception actuelle, vous devez éviter
Collectors.toMap
la source
J'ai légèrement modifié l'implémentation d'Emmanuel Touzery .
Cette version;
Tests unitaires:
la source
Désolé de rouvrir une ancienne question, mais comme elle a été modifiée récemment en disant que le «problème» reste toujours dans Java 11, j'ai eu envie de le souligner:
vous donne l'exception de pointeur null car la carte n'autorise pas null comme valeur. Cela a du sens car si vous recherchez une clé dans la carte
k
et qu'elle n'est pas présente, la valeur renvoyée l'est déjànull
(voir javadoc). Donc, si vous pouviez entrerk
la valeurnull
, la carte ressemblerait à un comportement étrange.Comme quelqu'un l'a dit dans les commentaires, il est assez facile de résoudre ce problème en utilisant le filtrage:
de cette façon, aucune
null
valeur ne sera insérée dans la carte, et vous obtiendrez TOUJOURSnull
la "valeur" lorsque vous recherchez un identifiant qui n'a pas de réponse dans la carte.J'espère que cela a du sens pour tout le monde.
la source
answerMap.put(4, null);
sans aucun problème. Vous avez raison: avec votre solution proposée, vous obtiendrez le même résultat pour anserMap.get () s'il n'est pas présent comme si la valeur serait insérée comme nulle. Cependant, si vous parcourez toutes les entrées de la carte, il y a évidemment une différence.la source
Conserver tous les identifiants des questions avec un petit ajustement
la source
NullPointerException est de loin l'exception la plus fréquemment rencontrée (du moins dans mon cas). Pour éviter cela, je vais sur la défensive et j'ajoute un tas de contrôles nuls et je finis par avoir du code gonflé et laid. Java 8 introduit Facultatif pour gérer les références nulles afin que vous puissiez définir des valeurs nullables et non nullables.
Cela dit, j'envelopperais toutes les références nullables dans le conteneur facultatif. Nous ne devons pas non plus rompre la compatibilité descendante également. Voici le code.
la source
Collectors.toMap()
valeurs non nulles