Java 8 NullPointerException Collectionneurs.toMap
La Java 8 Collectors.toMap
jette un NullPointerException
si une des valeurs est 'null'. Je ne comprends pas ce comportement, les cartes peuvent contenir des pointeurs null comme valeur sans aucun problème. Est-il une bonne raison pourquoi les valeurs ne peuvent pas être null pour Collectors.toMap
?
Aussi, est-il un gentil Java 8 chemin de la fixation de ce, ou devrais-je revenir à la plaine de vieux 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));
}
}
Stacktrace:
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 essayer Optional<Boolean>
? Sinon, split et utiliser le filtre.null
pourrait être un problème pour la clé, mais dans ce cas c'est la valeur.Pas toutes les cartes ont des problèmes avec
null
, HashMap
, par exemple, peut avoir un null
clé et le nombre de null
valeurs, vous pouvez essayer de créer un personnalisé Collector
à l'aide d'un HashMap
au lieu d'utiliser celui par défaut.Mais l'implémentation par défaut est
HashMap
- comme indiqué dans la première ligne de stacktrace. Le problème n'est pas qu'un Map
ne pouvez pas tenir null
valeur, mais que le deuxième argument de Map#merge
fonction ne peut pas être null.Personnellement, vu les circonstances, je voudrais aller avec les flux de solution, ou forEach() si l'entrée est parallèle. Le joli petit cours d'eau en fonction des solutions ci-dessous pourraient avoir une terrible de la performance.
OriginalL'auteur Jasper | 2014-07-08
Vous devez vous connecter pour publier un commentaire.
Vous pouvez contourner ce bug connu dans OpenJDK avec ceci:
Il n'est pas très joli, mais ça fonctionne. Résultat:
(cette tutoriel m'a le plus aidé.)
TreeMap
avec le passage d'un comparateur ànew
?oui, une définition d'un fournisseur (le premier argument) est une fonction qui passe aucun paramètre et renvoie un résultat, donc le lambda pour votre cas, serait
() -> new TreeMap<>(String.CASE_INSENSITIVE_ORDER)
pour créer un casseString
incrustéeTreeMap
.Cette réponse est la bonne, et à mon avis ce que le JDK devrait faire pour son défaut de non-version surchargée à la place. Peut-être que l'opération de fusion est plus rapide, mais je n'ai pas testé.
Je trouve cette réponse à chaque fois j'oublie comment contourner ce problème. JE VOUS REMERCIE.
Cela peut être très lent sur une grande entrée. Vous créez un
HashMap
et ensuite appelerputAll()
pour chaque entrée. Personnellement, étant donné les circonstances, je voudrais aller avec les flux de solution, ouforEach()
si l'entrée est parallèle.OriginalL'auteur kajacx
Il n'est pas possible avec les méthodes statiques de la
Collectors
. La javadoc detoMap
explique quetoMap
est basé surCarte.fusion
:et la javadoc de
Carte.fusion
dit:Vous pouvez éviter la boucle à l'aide de la
forEach
méthode de votre liste.mais il n'est pas vraiment simple que l'ancienne:
Il est précisé dans la javadoc de la fusion, mais il n'est pas indiqué dans la doc de toMap
Jamais pensé que les valeurs null dans la carte ferait un tel impact sur le standard de l'API, je préfère le considérer comme un défaut.
En fait l'API docs ne précise rien sur l'utilisation de
Map.merge
. Ce à mon humble avis est une faille dans la mise en œuvre qui limite parfaitement acceptable de cas d'utilisation qui a été négligée. Les méthodes surchargées detoMap
faire état de l'utilisation deMap.merge
mais pas celui de l'OP.il en est de même rapport de bug bugs.openjdk.java.net/browse/JDK-8148463
OriginalL'auteur gontard
J'ai écrit un
Collector
qui, à la différence de java par défaut, ne pas se bloquer lorsque vous aveznull
valeurs:Il suffit de remplacer votre
Collectors.toMap()
appel à un appel à cette fonction et ça va résoudre le problème.OriginalL'auteur Emmanuel Touzery
Yep, un retard de réponse de ma part, mais je pense que ça peut aider à comprendre ce qui se passe sous le capot, dans le cas où quelqu'un veut le code de certains autres
Collector
-logique.J'ai essayé de résoudre le problème en codant un plus naturel et simple d'approche. Je pense que c'est la plus directe possible:
Et les tests utilisant JUnit et assertj:
Et comment l'utilisez-vous? Ainsi, il suffit de l'utiliser à la place de
toMap()
comme le spectacle d'essai. Cela rend le code appelant look aussi propre que possible.OriginalL'auteur sjngm
Voici un peu plus simple que collectionneur proposé par @EmmanuelTouzery. L'utiliser si vous le souhaitez:
Nous venons de remplacer
null
avec certains d'objet personnalisénone
et faire l'opération inverse dans l'unité de finition.OriginalL'auteur Tagir Valeev
Si la valeur est une Chaîne de caractères, alors cela peut fonctionner:
map.entrySet().stream().collect(Collectors.toMap(e -> e.getKey(), e -> Optional.ofNullable(e.getValue()).orElse("")))
OriginalL'auteur Gnana
Selon la
Stacktrace
Quand est appelé le
map.merge
Il va faire une
null
vérifier que la première choseJe n'utilise pas Java 8, si souvent, donc je ne sais pas si il y a une meilleure façon de régler le problème, mais fix, c'est un peu dur.
Que vous pouvez faire:
Utiliser un filtre pour filtrer toutes les valeurs NULL, et dans le code Javascript de vérifier si le serveur n'a pas envoyer de réponse pour ce code signifie qu'il n'y répondez pas.
Quelque chose comme ceci:
Ou utiliser peek, qui est utilisé pour modifier le flux de l'élément par élément. À l'aide d'œil vous pouvez modifier la réponse à quelque chose de plus acceptable pour la carte mais elle est à dire modifier votre logique un peu.
Sons comme si vous voulez garder la conception actuelle, vous devriez éviter de
Collectors.toMap
OriginalL'auteur Marco Acierno
Le maintien de toutes les questions id avec petit tweak
OriginalL'auteur sigirisetti
OriginalL'auteur Igor Zubchenok
Désolé de rouvrir une vieille question, mais depuis qu'il a été édité récemment en disant que le "problème" reste en Java 11, j'ai senti que je tenais à préciser ceci:
vous donne l'exception de pointeur null parce que la carte ne permet pas la valeur null.
Cela a un sens, parce que si vous regardez une carte de la clé
k
et il n'est pas présent, alors la valeur retournée est déjànull
(voir javadoc). Donc, si vous avez été en mesure de mettre enk
la valeurnull
, la carte aurait l'air de se comporter bizarrement.Comme quelqu'un a dit dans les commentaires, il est assez facile de résoudre ce problème en utilisant un filtrage:
de cette façon, pas de
null
valeurs seront insérées dans la carte, et ENCORE, vous obtiendreznull
comme la "valeur" lors de la recherche d'un id qui n'a pas de réponse dans la carte.J'espère que cela a un sens pour tout le monde.
answerMap.put(4, null);
sans aucun problème. Vous avez raison, avec la solution que vous proposez, vous obtiendrez le même résultat pour anserMap.get() si ce n'est pas comme si la valeur doit être inséré, comme null. Toutefois, si vous itérer sur toutes les entrées de la carte il y a un évidemment une différence.OriginalL'auteur Luca
NullPointerException est de loin le plus fréquemment rencontré exception (au moins dans mon cas). Pour éviter cela, je aller sur la défensive et ajouter des tas de nulle vérifie et je finis par avoir de ballonnement et de laide du code. Java 8 introduit en Option pour gérer la valeur null références afin que vous pouvez définir les valeurs null et non nullable valeurs.
Cela dit, je voudrais de l'emballage de tous les nullable références, en Option, en conteneur. Nous devrions également de ne pas casser la compatibilité descendante. Voici le code.
Collectors.toMap()
pas les valeurs nullcalmer l'ami!! En s'appuyant sur les valeurs null n'est pas une bonne pratique. Si vous aviez utilisé en Option, vous n'auriez pas rencontré de NPE dans la première place. Lire sur Option usages.
et pourquoi est-ce? La valeur Null est bien, c'est sans-papiers de la bibliothèque qui est le problème. En option est sympa, mais pas partout.
OriginalL'auteur TriCore