특수한 포인터Collectors.toMap에 null 엔트리 값이 있는 예외가 있습니다.
Collectors.toMap
NullPointerException
중 가 " "인 null
이 동작은 이해할 수 없습니다.맵은 null 포인터를 값으로서 문제없이 포함할 수 있습니다."null"의 이 "이 될 수 없는 ?Collectors.toMap
또한 Java 8의 멋진 수정 방법이 있습니까?아니면 루프용 플레인 old로 되돌리는 것이 좋을까요?
문제의 예:
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에서도 발생합니다.
OpenJDK에서는 다음과 같은 방법으로 이 기존의 버그를 회피할 수 있습니다.
Map<Integer, Boolean> collect = list.stream()
.collect(HashMap::new, (m,v)->m.put(v.getId(), v.getAnswer()), HashMap::putAll);
그렇게 예쁘지는 않지만 효과가 있어요.결과:
1: true
2: true
3: null
(이 튜토리얼이 가장 큰 도움이 되었습니다.)
편집:
★★★★★★★★★★★★★★★와 달리Collectors.toMap
@mmdemirbas가 댓글로 지적한 것과 같이 같은 키를 여러 번 가지고 있으면 값이 자동으로 대체됩니다.원하지 않으면 댓글에 있는 링크를 보세요.
합니다.Collectors
의 javadoc은 다음과 같이 설명합니다.toMap
는 다음을 기반으로 합니다.
@ : @param merge에 값 간의 하기 위해 함수입니다
Map#merge(Object, Object, BiFunction)}
의 javadoc은 다음과 같이 말합니다.
@throws Null Pointer지정된 키가 null이고 이 맵이 null 키를 지원하지 않거나 값 또는 재매핑 기능이 null인 경우 예외입니다.
리스트의 방법을 사용하면 for 루프를 회피할 수 있습니다.
Map<Integer, Boolean> answerMap = new HashMap<>();
answerList.forEach((answer) -> answerMap.put(answer.getId(), answer.getAnswer()));
하지만 이것은 예전 방식보다 간단하지 않다.
Map<Integer, Boolean> answerMap = new HashMap<>();
for (Answer answer : answerList) {
answerMap.put(answer.getId(), answer.getAnswer());
}
가 글을 요.Collector
「」, 「」, 「」, 「」, 「」, 「」가 경우, .null
§:
public static <T, K, U>
Collector<T, ?, Map<K, U>> toMap(Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper) {
return Collectors.collectingAndThen(
Collectors.toList(),
list -> {
Map<K, U> result = new HashMap<>();
for (T item : list) {
K key = keyMapper.apply(item);
if (result.putIfAbsent(key, valueMapper.apply(item)) != null) {
throw new IllegalStateException(String.format("Duplicate key %s", key));
}
}
return result;
});
}
쓰세요.Collectors.toMap()
이 함수에 대한 호출을 호출하면 문제가 해결됩니다.
여기 @EmanuelTouzery가 제안한 것보다 다소 단순한 컬렉터가 있습니다.원하는 경우 사용:
public static <T, K, U> Collector<T, ?, Map<K, U>> toMapNullFriendly(
Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper) {
@SuppressWarnings("unchecked")
U none = (U) new Object();
return Collectors.collectingAndThen(
Collectors.<T, K, U> toMap(keyMapper,
valueMapper.andThen(v -> v == null ? none : v)), map -> {
map.replaceAll((k, v) -> v == none ? null : v);
return map;
});
}
교체만 하면 됩니다.null
" " " 를 사용하여" 를 합니다.none
이치노
만, 만약 다른 , 에서 무슨 일이 있는지 하는 것이 이 될 것 .Collector
- logic ()
저는 좀 더 네이티브하고 직선적인 접근 방식을 코딩하여 문제를 해결하려고 했습니다.가능한 한 직접적이라고 생각합니다.
public class LambdaUtilities {
/**
* In contrast to {@link Collectors#toMap(Function, Function)} the result map
* may have null values.
*/
public static <T, K, U, M extends Map<K, U>> Collector<T, M, M> toMapWithNullValues(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper) {
return toMapWithNullValues(keyMapper, valueMapper, HashMap::new);
}
/**
* In contrast to {@link Collectors#toMap(Function, Function, BinaryOperator, Supplier)}
* the result map may have null values.
*/
public static <T, K, U, M extends Map<K, U>> Collector<T, M, M> toMapWithNullValues(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper, Supplier<Map<K, U>> supplier) {
return new Collector<T, M, M>() {
@Override
public Supplier<M> supplier() {
return () -> {
@SuppressWarnings("unchecked")
M map = (M) supplier.get();
return map;
};
}
@Override
public BiConsumer<M, T> accumulator() {
return (map, element) -> {
K key = keyMapper.apply(element);
if (map.containsKey(key)) {
throw new IllegalStateException("Duplicate key " + key);
}
map.put(key, valueMapper.apply(element));
};
}
@Override
public BinaryOperator<M> combiner() {
return (left, right) -> {
int total = left.size() + right.size();
left.putAll(right);
if (left.size() < total) {
throw new IllegalStateException("Duplicate key(s)");
}
return left;
};
}
@Override
public Function<M, M> finisher() {
return Function.identity();
}
@Override
public Set<Collector.Characteristics> characteristics() {
return Collections.unmodifiableSet(EnumSet.of(Collector.Characteristics.IDENTITY_FINISH));
}
};
}
}
JUnit 및 assertj를 사용한 테스트:
@Test
public void testToMapWithNullValues() throws Exception {
Map<Integer, Integer> result = Stream.of(1, 2, 3)
.collect(LambdaUtilities.toMapWithNullValues(Function.identity(), x -> x % 2 == 1 ? x : null));
assertThat(result)
.isExactlyInstanceOf(HashMap.class)
.hasSize(3)
.containsEntry(1, 1)
.containsEntry(2, null)
.containsEntry(3, 3);
}
@Test
public void testToMapWithNullValuesWithSupplier() throws Exception {
Map<Integer, Integer> result = Stream.of(1, 2, 3)
.collect(LambdaUtilities.toMapWithNullValues(Function.identity(), x -> x % 2 == 1 ? x : null, LinkedHashMap::new));
assertThat(result)
.isExactlyInstanceOf(LinkedHashMap.class)
.hasSize(3)
.containsEntry(1, 1)
.containsEntry(2, null)
.containsEntry(3, 3);
}
@Test
public void testToMapWithNullValuesDuplicate() throws Exception {
assertThatThrownBy(() -> Stream.of(1, 2, 3, 1)
.collect(LambdaUtilities.toMapWithNullValues(Function.identity(), x -> x % 2 == 1 ? x : null)))
.isExactlyInstanceOf(IllegalStateException.class)
.hasMessage("Duplicate key 1");
}
@Test
public void testToMapWithNullValuesParallel() throws Exception {
Map<Integer, Integer> result = Stream.of(1, 2, 3)
.parallel() // this causes .combiner() to be called
.collect(LambdaUtilities.toMapWithNullValues(Function.identity(), x -> x % 2 == 1 ? x : null));
assertThat(result)
.isExactlyInstanceOf(HashMap.class)
.hasSize(3)
.containsEntry(1, 1)
.containsEntry(2, null)
.containsEntry(3, 3);
}
@Test
public void testToMapWithNullValuesParallelWithDuplicates() throws Exception {
assertThatThrownBy(() -> Stream.of(1, 2, 3, 1, 2, 3)
.parallel() // this causes .combiner() to be called
.collect(LambdaUtilities.toMapWithNullValues(Function.identity(), x -> x % 2 == 1 ? x : null)))
.isExactlyInstanceOf(IllegalStateException.class)
.hasCauseExactlyInstanceOf(IllegalStateException.class)
.hasStackTraceContaining("Duplicate key");
}
리고고 ?떻 ?? ???? 그럼 ㅇ, ㅇ, ㅇ, ㅇ, ㅇ, ㅇ, ㅇ, ㅇ, ㅇ, ㅇ, ㅇ, toMap()
, 가 가능한한 것처럼 됩니다.이것에 의해, 발신 코드가 가능한 한 깨끗한 것처럼 보이게 됩니다.
편집:
아래 Holger의 아이디어를 구현하고 테스트 방법을 추가했습니다.
Emmanuel Touzery의 늘세이프 맵 구현을 약간 수정했습니다.
이 버전:
- null 키를 허용합니다.
- null 값을 허용합니다.
- 중복된 키를 검출하여(늘인 경우에도), 송신합니다.
IllegalStateException
원래 JDK 구현과 같이 - 키가 null 값에 이미 매핑되어 있는 경우에도 중복 키를 검출합니다.즉, null-value와 no-mapping을 구분합니다.
public static <T, K, U> Collector<T, ?, Map<K, U>> toMapOfNullables(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper) {
return Collectors.collectingAndThen(
Collectors.toList(),
list -> {
Map<K, U> map = new LinkedHashMap<>();
list.forEach(item -> {
K key = keyMapper.apply(item);
U value = valueMapper.apply(item);
if (map.containsKey(key)) {
throw new IllegalStateException(String.format(
"Duplicate key %s (attempted merging values %s and %s)",
key, map.get(key), value));
}
map.put(key, value);
});
return map;
}
);
}
유닛 테스트:
@Test
public void toMapOfNullables_WhenHasNullKey() {
assertEquals(singletonMap(null, "value"),
Stream.of("ignored").collect(Utils.toMapOfNullables(i -> null, i -> "value"))
);
}
@Test
public void toMapOfNullables_WhenHasNullValue() {
assertEquals(singletonMap("key", null),
Stream.of("ignored").collect(Utils.toMapOfNullables(i -> "key", i -> null))
);
}
@Test
public void toMapOfNullables_WhenHasDuplicateNullKeys() {
assertThrows(new IllegalStateException("Duplicate key null"),
() -> Stream.of(1, 2, 3).collect(Utils.toMapOfNullables(i -> null, i -> i))
);
}
@Test
public void toMapOfNullables_WhenHasDuplicateKeys_NoneHasNullValue() {
assertThrows(new IllegalStateException("Duplicate key duplicated-key"),
() -> Stream.of(1, 2, 3).collect(Utils.toMapOfNullables(i -> "duplicated-key", i -> i))
);
}
@Test
public void toMapOfNullables_WhenHasDuplicateKeys_OneHasNullValue() {
assertThrows(new IllegalStateException("Duplicate key duplicated-key"),
() -> Stream.of(1, null, 3).collect(Utils.toMapOfNullables(i -> "duplicated-key", i -> i))
);
}
@Test
public void toMapOfNullables_WhenHasDuplicateKeys_AllHasNullValue() {
assertThrows(new IllegalStateException("Duplicate key duplicated-key"),
() -> Stream.of(null, null, null).collect(Utils.toMapOfNullables(i -> "duplicated-key", i -> i))
);
}
값이 String일 경우 다음과 같이 동작할 수 있습니다. map.entrySet().stream().collect(Collectors.toMap(e -> e.getKey(), e -> Optional.ofNullable(e.getValue()).orElse("")))
에 따르면Stacktrace
Exception in thread "main" java.lang.NullPointerException
at java.util.HashMap.merge(HashMap.java:1216)
at java.util.stream.Collectors.lambda$toMap$148(Collectors.java:1320)
at java.util.stream.Collectors$$Lambda$5/391359742.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 com.guice.Main.main(Main.java:28)
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)
이 라고 불릴 때map.merge
BiConsumer<M, T> accumulator
= (map, element) -> map.merge(keyMapper.apply(element),
valueMapper.apply(element), mergeFunction);
그것은 할 수 있다null
제일 먼저 확인하다
if (value == null)
throw new NullPointerException();
자바8을 자주 사용하지 않기 때문에 더 좋은 방법이 있을지 모르겠지만, 조금 어렵습니다.
다음과 같은 작업을 할 수 있습니다.
필터를 사용하여 모든 NULL 값을 필터링합니다.Javascript 코드에서 서버가 이 ID에 대한 응답을 보내지 않았는지 확인하는 것은 그가 회신하지 않았음을 의미합니다.
다음과 같은 경우:
Map<Integer, Boolean> answerMap =
answerList
.stream()
.filter((a) -> a.getAnswer() != null)
.collect(Collectors.toMap(Answer::getId, Answer::getAnswer));
또는 요소의 스트림 요소를 변경하는 데 사용되는 peek를 사용합니다.peek를 사용하면 맵에 적합한 것으로 답변을 변경할 수 있지만 논리를 약간 편집해야 합니다.
현재의 설계를 유지하려면 피해야 할 것 같습니다.Collectors.toMap
public static <T, K, V> Collector<T, HashMap<K, V>, HashMap<K, V>> toHashMap(
Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends V> valueMapper
)
{
return Collector.of(
HashMap::new,
(map, t) -> map.put(keyMapper.apply(t), valueMapper.apply(t)),
(map1, map2) -> {
map1.putAll(map2);
return map1;
}
);
}
public static <T, K> Collector<T, HashMap<K, T>, HashMap<K, T>> toHashMap(
Function<? super T, ? extends K> keyMapper
)
{
return toHashMap(keyMapper, Function.identity());
}
오래된 질문을 다시 하게 되어 죄송합니다만, 최근 Java 11에서 "문제"가 아직 남아 있다고 편집되었기 때문에 이 점을 지적하고 싶었습니다.
answerList
.stream()
.collect(Collectors.toMap(Answer::getId, Answer::getAnswer));
맵에서는 null을 값으로 사용할 수 없기 때문에에서는 늘 포인터의 예외가 표시됩니다.지도에서 키를 찾으면 이치에 맞기 때문입니다.k
존재하지 않는 경우 반환된 값은 이미 존재합니다.null
(javadoc 참조).그래서 만약 당신이 그 안에k
가치null
지도는 이상하게 보일 거예요
댓글에서 말한 것처럼 필터링을 사용하여 이 문제를 해결하는 것은 매우 쉽습니다.
answerList
.stream()
.filter(a -> a.getAnswer() != null)
.collect(Collectors.toMap(Answer::getId, Answer::getAnswer));
이렇게 해서 안 된다null
값이 맵에 삽입되지만 여전히 값이 표시됩니다.null
맵에 답이 없는 ID를 찾을 때 "값"으로 지정합니다.
이게 모두에게 이해가 됐으면 좋겠어요.
작은 수정으로 모든 질문 ID 유지
Map<Integer, Boolean> answerMap =
answerList.stream()
.collect(Collectors.toMap(Answer::getId, a ->
Boolean.TRUE.equals(a.getAnswer())));
완전성을 위해 mergeFunction 파라미터를 사용하여 toMapOfNullables 버전을 게시합니다.
public static <T, K, U> Collector<T, ?, Map<K, U>> toMapOfNullables(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper, BinaryOperator<U> mergeFunction) {
return Collectors.collectingAndThen(Collectors.toList(), list -> {
Map<K, U> result = new HashMap<>();
for(T item : list) {
K key = keyMapper.apply(item);
U newValue = valueMapper.apply(item);
U value = result.containsKey(key) ? mergeFunction.apply(result.get(key), newValue) : newValue;
result.put(key, value);
}
return result;
});
}
언급URL : https://stackoverflow.com/questions/24630963/nullpointerexception-in-collectors-tomap-with-null-entry-values
'it-source' 카테고리의 다른 글
봄에 자기 인스턴스화 개체에 종속성을 주입하려면 어떻게 해야 합니까? (0) | 2022.11.20 |
---|---|
행을 찾을 수 없는 경우 JOIN 조건의 폴백 값 (0) | 2022.11.20 |
Gzip의 JavaScript 구현 (0) | 2022.11.20 |
개체 목록 섞기 (0) | 2022.11.20 |
MySql 최대 메모리 용량이 위험할 정도로 높아도 증대가 필요함 (0) | 2022.11.19 |