Java 8 Collectors.toMap
выбрасывает, NullPointerException
если одно из значений 'null'. Я не понимаю этого поведения, карты могут содержать нулевые указатели в качестве значения без каких-либо проблем. Есть ли веская причина, почему значения не могут быть нулевыми для Collectors.toMap
?
Кроме того, есть хороший способ исправить это в Java 8, или я должен вернуться к простой старой для цикла?
Пример моей проблемы:
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));
}
}
Трассировки стека:
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)
Эта проблема все еще существует в Java 11.
null
всегда было немного проблематично, как в TreeMap. Может быть, хороший момент, чтобы попробоватьOptional<Boolean>
? В противном случае разделите и используйте фильтр.null
может быть проблемой для ключа, но в этом случае это значение.null
,HashMap
например, могут иметь одинnull
ключ и любое количествоnull
значений, вы можете попробовать создать пользовательский,Collector
используяHashMap
вместо того, чтобы использовать по умолчанию.HashMap
- как показано в первой строке stacktrace. Проблема не в том, что значениеMap
не может содержатьnull
, а в том, что второй аргументMap#merge
функции не может быть нулевым.Ответы:
Вы можете обойти эту известную ошибку в OpenJDK с помощью этого:
Это не так красиво, но это работает. Результат:
( этот урок помог мне больше всего.)
источник
() -> new TreeMap<>(String.CASE_INSENSITIVE_ORDER)
в созданииString
ключа без учета регистраTreeMap
.Map<Integer, Boolean> collect = list.stream().collect(HashMap<Integer, Boolean>::new, (m,v)->m.put(v.getId(), v.getAnswer()), HashMap<Integer, Boolean>::putAll);
. У меня было: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
а затем вызываетеputAll()
для каждой записи. Лично при определенных обстоятельствах я бы выбрал не потоковое решение, илиforEach()
если вход параллельный.Это невозможно при использовании статических методов
Collectors
. Javadoc вtoMap
объясняет, чтоtoMap
основано наMap.merge
:и Javadoc
Map.merge
говорит:Вы можете избежать цикла for, используя
forEach
метод из вашего списка.но это не так просто, как по старинке
источник
Map.merge
. Это IMHO - недостаток в реализации, который ограничивает совершенно приемлемый вариант использования, который был упущен. Перегруженные методыtoMap
do устанавливают использование,Map.merge
но не тот, который использует OP.Я написал,
Collector
который, в отличие от java по умолчанию, не падает, когда у вас естьnull
значения:Просто замените свой
Collectors.toMap()
вызов на вызов этой функции, и это решит проблему.источник
null
ценностей и их использованиеputIfAbsent
плохо сочетаются друг с другом. Он не обнаруживает дубликаты ключей, когда они отображаются наnull
...Да, поздний ответ от меня, но я думаю, что это может помочь понять, что происходит под капотом в случае, если кто-то захочет кодировать какой-то другой
Collector
-логический.Я пытался решить эту проблему, используя более естественный и прямой подход. Я думаю, что это максимально прямо:
И тесты, использующие JUnit и assertj:
И как ты это используешь? Ну, просто используйте его вместо того,
toMap()
как показывают тесты. Это делает вызывающий код максимально чистым.РЕДАКТИРОВАТЬ:
реализовал идею Хольгера ниже, добавил метод тестирования
источник
(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()
самом деле это проверяет. Может быть, я должен сделать несколько параллельных потоков один раз :)Вот несколько более простой сборщик, чем предложенный @EmmanuelTouzery. Используйте его, если вам нравится:
Мы просто заменим
null
какой-то пользовательский объектnone
и сделаем обратную операцию в финишере.источник
Если значение является строкой, это может работать:
map.entrySet().stream().collect(Collectors.toMap(e -> e.getKey(), e -> Optional.ofNullable(e.getValue()).orElse("")))
источник
Согласно
Stacktrace
Когда называется
map.merge
Он будет делать
null
проверку , как первое ,Я не использую Java 8 так часто, поэтому я не знаю, есть ли лучший способ исправить это, но исправить это немного сложно.
Вы могли бы сделать:
Используйте фильтр, чтобы отфильтровать все значения NULL, и в коде Javascript проверьте, не отправил ли сервер какой-либо ответ на этот идентификатор, что означает, что он не ответил на него.
Что-то вроде этого:
Или используйте peek, который используется для изменения элемента потока для элемента. Используя peek, вы можете изменить ответ на что-то более приемлемое для карты, но это означает, что нужно немного изменить логику.
Похоже, если вы хотите сохранить текущий дизайн, вы должны избегать
Collectors.toMap
источник
Я немного изменил реализацию Эммануэля Тузери .
Эта версия;
Модульные тесты:
источник
Извините, что снова открыл старый вопрос, но так как он был отредактирован недавно, сказав, что «проблема» все еще остается в Java 11, я почувствовал, что хочу указать на это:
дает исключение нулевого указателя, потому что карта не допускает нулевое значение в качестве значения. Это имеет смысл, потому что если вы ищете на карте ключ,
k
а его нет, то возвращаемое значение уже естьnull
(см. Javadoc). Так что, если бы вы смогли ввестиk
значениеnull
, карта выглядела бы странно.Как кто-то сказал в комментариях, это довольно легко решить с помощью фильтрации:
таким образом, никакие
null
значения не будут вставлены в карту, и ВСЕ ЕЩЕ вы получитеnull
как «значение» при поиске идентификатора, который не имеет ответа на карте.Я надеюсь, что это имеет смысл для всех.
источник
answerMap.put(4, null);
без проблем. Вы правы в том, что с вашим предлагаемым решением вы получите тот же результат для anserMap.get (), если его нет, как если бы значение было вставлено как нулевое. Однако, если вы перебираете все записи на карте, очевидно, есть разница.источник
Сохранение всех идентификаторов вопросов с небольшой настройкой
источник
NullPointerException - безусловно, наиболее часто встречающееся исключение (по крайней мере, в моем случае). Чтобы избежать этого, я иду в оборону и добавляю кучу пустых проверок, и в итоге получаю вздутый и уродливый код. В Java 8 добавлен Optional для обработки пустых ссылок, так что вы можете определять значения, допускающие значения NULL и NULL.
Тем не менее, я бы обернул все пустые ссылки в дополнительный контейнер. Мы также не должны нарушать обратную совместимость. Вот код
источник
Collectors.toMap()