Collectors.toMap의 Java 8 NullPointerException


331

Java 8 은 값 중 하나가 'null'인 경우 Collectors.toMapa를 던집니다 NullPointerException. 이 동작을 이해하지 못합니다.지도에는 아무런 문제없이 null 포인터가 값으로 포함될 수 있습니다. 값이 널이 될 수없는 이유가 Collectors.toMap있습니까?

또한 이것을 고치는 멋진 Java 8 방법이 있습니까? 아니면 평범한 오래된 for 루프로 되돌려 야합니까?

내 문제의 예 :

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에 존재합니다.


5
nullTreeMap에서와 같이 항상 약간 문제가있었습니다. 시도해 볼 수있는 좋은 순간 Optional<Boolean>? 그렇지 않으면 필터를 분리하여 사용하십시오.
Joop Eggen

5
@JoopEggen null은 키에 문제가 될 수 있지만이 경우에는 가치입니다.
gontard

모든지도에 문제가있다 null, HashMap하나 가질 수 있습니다 예를 들어 null키와 임의의 수의 null값을 사용자 지정을 만드는 시도 할 수 있습니다 Collector사용 HashMap하는 대신 기본 하나를 사용하여.
kajacx

2
@kajacx 그러나 기본 구현은 HashMapstacktrace의 첫 번째 줄에서 볼 수 있습니다. 문제는 값을 Map보유 할 수 없다는 것이 아니라 함수 null의 두 번째 인수 Map#merge가 널이 될 수 없다는 것입니다.
czerny

개인적으로 주어진 상황에서 비 스트림 솔루션을 사용하거나 입력이 병렬 인 경우 forEach ()를 사용합니다. 아래의 멋진 짧은 스트림 기반 솔루션은 끔찍한 성능을 가질 수 있습니다.
Ondra Žižka

답변:


301

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

( 튜토리얼 가장 도움 되었습니다.)


3
@Jagger 네, 공급자의 정의 (첫 번째 인수)는 매개 변수를 전달하지 않고 결과를 반환하는 함수이므로 () -> new TreeMap<>(String.CASE_INSENSITIVE_ORDER)대소 문자를 구분하지 않는 Stringkeyed 를 만드는 것이 TreeMap좋습니다.
Brett Ryan

2
이것은 정답이며, JDK가 기본 오버로드되지 않은 버전에 대해 수행해야 할 작업입니다. 아마도 병합이 더 빠르지 만 테스트하지는 않았습니다.
Brett Ryan

1
컴파일하려면 형식 매개 변수를 지정해야했습니다 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)
Anthony O.

2
큰 입력에서는 상당히 느릴 수 있습니다. 를 생성 한 HashMap다음 putAll()모든 단일 항목 을 호출 합니다. 개인적으로 주어진 상황에서 비 스트림 솔루션을 사용하거나 forEach()입력이 병렬 인 경우에 사용합니다.
Ondra Žižka

3
이 솔루션은 원래 toMap 구현과 다르게 작동합니다. 원래 구현은 중복 키를 감지하고 IllegalStatException을 발생 시키지만이 솔루션은 최신 키를 자동으로 승인합니다. Emmanuel Touzery의 솔루션 ( stackoverflow.com/a/32648397/471214 )은 원래의 행동에 더 가깝습니다.
mmdemirbas

174

의 정적 메소드로는 불가능합니다 Collectors. 의 javadoc는 toMap다음을 toMap기반으로 설명합니다 Map.merge.

@param mergeFunction은 제공된 것과 동일한 키와 관련된 값 사이의 충돌을 해결하는 데 사용되는 병합 함수입니다. Map#merge(Object, Object, BiFunction)}

의 javadoc는 Map.merge말합니다 :

지정된 키가 널이고이 맵이 널 키를 지원하지 않거나 또는 remappingFunction 널인 경우 @throws NullPointerException

forEach리스트 의 메소드를 사용하여 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());
}

3
이 경우에는 구식 for-each를 사용합니다. toMerge에서이 버그를 고려해야합니까? 이 병합 함수의 사용은 실제로 구현 세부 사항이거나 toMap이 null 값을 처리하지 못하게하는 좋은 이유입니까?
재스퍼

6
그것은 병합의 javadoc에 지정되어 있지만 toMap의 문서에는 명시되어 있지 않습니다.
Jasper

119
map의 null 값이 표준 API에 영향을 줄 것이라고 생각하지 않았으며 결함으로 간주합니다.
Askar Kalykov

16
실제로 API 문서는의 사용에 대해 아무 것도 언급하지 않습니다 Map.merge. 이 IMHO는 간과 된 완벽하게 수용 가능한 사용 사례를 제한하는 구현상의 결함입니다. 오버로드 된 방법은 OP가 사용하고 있지만 toMap사용 Map.merge하지는 않습니다.
Brett Ryan

11
@ 재스퍼 버그 보고서도 있습니다 bugs.openjdk.java.net/browse/JDK-8148463
Pixel

23

내가 쓴 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()이 함수를 호출에 전화를하고이 문제를 해결할 수 있습니다.


1
그러나 null가치를 허용 하고 사용하는 putIfAbsent것은 잘 이루어지지 않습니다. 다음에 매핑 될 때 중복 키를 감지하지 못합니다 null.
Holger

10

그렇다. 나의 대답은 늦었지만 누군가가 다른 Collector논리 를 코딩하려는 경우 후드에서 일어나는 일을 이해하는 데 도움이 될 수 있다고 생각한다 .

더 원시적이고 직접적인 접근 방식을 코딩하여 문제를 해결하려고했습니다. 가능한 한 직접적이라고 생각합니다.

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 아이디어를 구현하고 테스트 방법을 추가했습니다.


1
결합기는 중복 키를 확인하지 않습니다. 모든 키를 확인하지 않으려면 다음과 같이 사용할 수 있습니다.(map1, map2) -> { int total = map1.size() + map2.size(); map1.putAll(map2); if(map1.size() < total.size()) throw new IllegalStateException("Duplicate key(s)"); return map1; }
Holger

@Holger Yep, 맞습니다. 특히 accumulator()실제로 확인하기 때문에. 어쩌면 내가 : 한 번에 몇 가지 병렬 스트림해야
sjngm

7

@EmmanuelTouzery가 제안한 것보다 다소 간단한 수집기가 있습니다. 원하는 경우 사용하십시오 :

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마무리 장치에서 역 작업을 수행합니다.


5

값이 문자열이면 다음과 같이 작동합니다. map.entrySet().stream().collect(Collectors.toMap(e -> e.getKey(), e -> Optional.ofNullable(e.getValue()).orElse("")))


4
데이터 수정에 문제가없는 경우에만 작동합니다. 다운 스트림 메서드는 빈 문자열이 아닌 null 값을 기대할 수 있습니다.
Sam Buchmiller 2016 년

3

에 따르면 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();

Java 8을 자주 사용하지 않으므로 문제를 해결하는 더 좋은 방법이 있는지 알지 못하지만 조금 어렵습니다.

당신은 할 수 있습니다 :

filter를 사용하여 모든 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


3

Emmanuel Touzery의 구현 을 약간 수정했습니다 .

이 버전;

  • 널 키 허용
  • null 값을 허용합니다
  • 중복 키를 발견하고 (널인 경우에도) 원래 JDK 구현에서와 같이 IllegalStateException을 발생시킵니다.
  • 키가 이미 널값에 맵핑 된 경우에도 중복 키를 감지합니다. 즉, 널값이있는 맵핑을 맵핑하지 않음에서 분리합니다.
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);
                if (map.containsKey(key)) {
                    throw new IllegalStateException(String.format("Duplicate key %s", key));
                }
                map.put(key, valueMapper.apply(item));
            });
            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))
    );
}

1

오래된 질문을 다시 열어서 죄송하지만 최근에 "문제"가 Java 11에 남아 있다고 편집되어 최근에 다음과 같이 지적하고 싶습니다.

answerList
        .stream()
        .collect(Collectors.toMap(Answer::getId, Answer::getAnswer));

지도에서 null을 값으로 허용하지 않으므로 null 포인터 예외가 발생합니다. 키에 대한 맵을보고 키 k가없는 경우 리턴 된 값이 이미 있으므로 null(javadoc 참조) 이는 의미가 있습니다. 따라서 k값 을 입력 할 수 있으면 null지도가 이상하게 동작하는 것처럼 보입니다.

누군가가 의견에서 말했듯이 필터링을 사용 하여이 문제를 해결하는 것은 매우 쉽습니다.

answerList
        .stream()
        .filter(a -> a.getAnswer() != null)
        .collect(Collectors.toMap(Answer::getId, Answer::getAnswer));

이런 식으로 null맵에 값이 삽입 되지 않으며 여전히 맵에 null응답이없는 ID를 찾을 때 "값"으로 표시됩니다.

나는 이것이 모든 사람들에게 의미가 있기를 바랍니다.


1
지도가 null 값을 허용하지 않으면 의미가 있지만 그렇지 않습니다. answerMap.put(4, null);아무 문제없이 할 수 있습니다 . 제안 된 솔루션을 사용하면 anserMap.get ()이없는 경우 값이 null로 삽입되는 것과 동일한 결과를 얻을 수 있습니다. 그러나 맵의 모든 항목을 반복하면 분명히 차이가 있습니다.
재스퍼

1
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());
}

1
이것이 컴파일되기 때문에 공감. Map :: putAll에 반환 값이 없으므로 허용되는 답변이 컴파일되지 않습니다.
Taugenichts

0

약간의 조정으로 모든 질문 ID 유지

Map<Integer, Boolean> answerMap = 
  answerList.stream()
            .collect(Collectors.toMap(Answer::getId, a -> 
                       Boolean.TRUE.equals(a.getAnswer())));

이것이 가장 좋은 답변이라고 생각합니다. 가장 간결한 답변이며 NPE 문제를 해결합니다.
LConrad

-3

NullPointerException은 가장 빈번하게 발생하는 예외입니다 (적어도 필자의 경우). 이것을 피하기 위해 방어 적이며 null 검사를 추가하면 부풀어 오르고 추한 코드가 생깁니다. Java 8에는 널 (null) 값과 널 (null) 값을 정의 할 수 있도록 널 참조를 처리하는 선택적이 도입되었습니다.

즉, 선택적 컨테이너에 모든 nullable 참조를 래핑합니다. 또한 이전 버전과의 호환성도 깨뜨리지 않아야합니다. 코드는 다음과 같습니다.

class Answer {
    private int id;
    private Optional<Boolean> answer;

    Answer() {
    }

    Answer(int id, Boolean answer) {
        this.id = id;
        this.answer = Optional.ofNullable(answer);
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    /**
     * Gets the answer which can be a null value. Use {@link #getAnswerAsOptional()} instead.
     *
     * @return the answer which can be a null value
     */
    public Boolean getAnswer() {
        // What should be the default value? If we return null the callers will be at higher risk of having NPE
        return answer.orElse(null);
    }

    /**
     * Gets the optional answer.
     *
     * @return the answer which is contained in {@code Optional}.
     */
    public Optional<Boolean> getAnswerAsOptional() {
        return answer;
    }

    /**
     * Gets the answer or the supplied default value.
     *
     * @return the answer or the supplied default value.
     */
    public boolean getAnswerOrDefault(boolean defaultValue) {
        return answer.orElse(defaultValue);
    }

    public void setAnswer(Boolean answer) {
        this.answer = Optional.ofNullable(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 with optional answers (i.e. with null)
        Map<Integer, Optional<Boolean>> answerMapWithOptionals = answerList.stream()
                .collect(Collectors.toMap(Answer::getId, Answer::getAnswerAsOptional));

        // map in which null values are removed
        Map<Integer, Boolean> answerMapWithoutNulls = answerList.stream()
                .filter(a -> a.getAnswerAsOptional().isPresent())
                .collect(Collectors.toMap(Answer::getId, Answer::getAnswer));

        // map in which null values are treated as false by default
        Map<Integer, Boolean> answerMapWithDefaults = answerList.stream()
                .collect(Collectors.toMap(a -> a.getId(), a -> a.getAnswerOrDefault(false)));

        System.out.println("With Optional: " + answerMapWithOptionals);
        System.out.println("Without Nulls: " + answerMapWithoutNulls);
        System.out.println("Wit Defaults: " + answerMapWithDefaults);
    }
}

1
쓸모없는 대답, 왜이 문제를 해결하기 위해 null을 제거해야합니까? 이것은 Collectors.toMap()null 값 이 아닌 문제입니다
Enerccio

@Enerccio는 친구를 진정시킵니다 !! 널값에 의존하는 것은 좋은 습관이 아닙니다. Optional을 사용했다면 처음에는 NPE가 발생하지 않았을 것입니다. 선택적 사용법을 읽으십시오.
TriCore

1
왜 그런데? 널값은 문제가되지 않는 문서화되지 않은 라이브러리입니다. 선택 사항은 훌륭하지만 어디에나 없습니다.
Enerccio
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.