Java 8 NullPointerException в Collectors.toMap


331

Java 8 Collectors.toMapвикидає a, 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.


5
nullзавжди було трохи проблематично, як у TreeMap. Може, приємний момент спробувати Optional<Boolean>? В іншому випадку розділіть і використовуйте фільтр.
Joop Eggen

5
@JoopEggen nullможе бути проблемою для ключа, але в цьому випадку це значення.
gontard

Не всі карти мають проблеми null, HashMapнаприклад, вони можуть мати один nullключ і будь-яку кількість nullзначень, ви можете спробувати створити спеціальний, Collectorвикористовуючи HashMapзамість стандартної.
kajacx

2
@kajacx Але реалізацією за замовчуванням є HashMap- як показано в першому рядку стека. Проблема полягає не в тому, що значення Mapне може утримувати null, а в тому, що другий аргумент Map#mergeфункції не може бути нульовим.
czerny

Особисто, за даних обставин, я б перейшов до потокового рішення, або дляEEEE (), якщо вхід паралельний. Приємні рішення на основі короткого потоку нижче можуть мати жахливі результати.
Ондра Жижка

Відповіді:


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)щоб створити Stringключ, нечутливий до регістру TreeMap.
Бретт Райан

2
Це правильна відповідь, і IMHO, що повинен робити JDK для своєї неперевантаженої версії за замовчуванням. Можливо, злиття швидше, але я не перевіряв.
Бретт Райан

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)
Ентоні О.

2
Це може бути досить повільним при великому вході. Ви створюєте HashMapі потім дзвоните putAll()за кожним записом. Особисто, за певних обставин, я б перейшов до потокового рішення, або forEach()якщо вхід паралельний.
Ондра Жижка

3
Будьте уважні, що це рішення поводиться інакше, ніж оригінальна реалізація для карт. Оригінальна реалізація виявляє повторювані ключі та викидає IllegalStatException, але це рішення мовчки приймає останній ключ. Рішення Еммануель Touzery ( в stackoverflow.com/a/32648397/471214 ) ближче до вихідного поведінки.
mmdemirbas

174

Неможливо статичними методами Collectors. Явадок toMapпояснює, що toMapбазується на Map.merge:

@param mergeFunction функція злиття, яка використовується для вирішення зіткнень між значеннями, пов'язаними з тим самим ключем, що постачається Map#merge(Object, Object, BiFunction)}

і javadoc Map.mergeкаже:

@throws NullPointerException, якщо вказаний ключ є нульовим, і ця карта не підтримує нульові клавіші або значення або перестановкаFunction є null

Ви можете уникнути циклу for, використовуючи forEachметод свого списку.

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
У такому випадку я б скоріше використовував старомодний для кожного. Чи слід вважати це помилкою в toMerge? оскільки використання цієї функції злиття насправді є детальною інформацією про реалізацію, чи це вагоме міркування для того, щоб не дозволяти ToMap обробляти нульові значення?
Джаспер

6
Він вказаний у javadoc злиття, але це не зазначено в документі toMap
Jasper

119
Ніколи не думав, що нульові значення на карті вплинуть на стандартний API, я вважаю його недоліком.
Аскар Каликов

16
Насправді документи API нічого не говорять про використання Map.merge. Цей ІМХО є недоліком в реалізації, що обмежує цілком прийнятний випадок використання, який було проігноровано. Перевантажені методи дійсно toMapзаявляють про використання, Map.mergeале не того, яким використовується ОП.
Бретт Райан

11
@Jasper є навіть повідомлення про помилку bugs.openjdk.java.net/browse/JDK-8148463
піксель

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()як показують тести. Це робить код виклику максимально чистим.

EDIT:
реалізував ідею Холгера нижче, додав метод тестування


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 Так, це правда. Тим більше, що 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

Якщо значення - це String, то це може працювати: map.entrySet().stream().collect(Collectors.toMap(e -> e.getKey(), e -> Optional.ofNullable(e.getValue()).orElse("")))


4
Це працює лише в тому випадку, якщо ви не в порядку зі зміною даних. Нижчі методи можуть очікувати нульових значень, а не порожніх рядків.
Сем Бухміллер

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 так часто, тому я не знаю, чи є кращий спосіб виправити це, але виправити це трохи важко.

Ви можете зробити:

Використовуйте фільтр для фільтрації всіх значень NULL, і в коді Javascript перевірте, чи сервер не надіслав відповідь на цей ідентифікатор, це означає, що він не відповів на нього.

Щось на зразок цього:

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

Або використовувати peek, який використовується для зміни елемента потоку для елемента. За допомогою peek ви можете змінити відповідь на щось більш прийнятне для карти, але це означає трохи відредагувати свою логіку.

Схоже, якщо ви хочете зберегти сучасний дизайн, якого вам слід уникати Collectors.toMap


3

Я трохи змінив реалізацію Еммануеля Тузері .

Ця версія;

  • Дозволяє нульові клавіші
  • Дозволяє нульових значень
  • Виявляє повторювані ключі (навіть якщо вони є нульовими) та видаляє IllegalStateException, як у оригінальній реалізації JDK.
  • Виявляє повторювані ключі також тоді, коли ключ вже відображений у нульове значення. Іншими словами, відокремлює відображення з нульовим значенням від 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);
                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 як значення. Це має сенс, тому що якщо ви шукаєте на карті ключ kта його немає, то повернене значення вже є null(див. Javadoc). Тож якби вам вдалося ввести kзначення null, карта виглядала б так, як вона дивно поводиться.

Як хтось сказав у коментарях, вирішити це досить просто за допомогою фільтрації:

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

таким чином ніякі nullзначення не будуть вставлені в карту, і ВИНАГИ ви отримаєте nullяк "значення" при пошуку ідентифікатора, який не має відповіді на карті.

Сподіваюся, це має сенс для всіх.


1
Було б сенс, якби карта не дозволяла нульових значень, але це робить. Можна обійтися answerMap.put(4, null);без проблем. Ви маєте рацію, що із запропонованим рішенням ви отримаєте такий самий результат для anserMap.get (), якщо його немає, як якщо б значення було вставлено як нулеве. Однак якщо ви повторите всі записи на карті, очевидно, є різниця.
Джаспер

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

Збереження ідентифікаторів усіх питань з невеликим налаштуванням

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

Я думаю, що це найкраща відповідь - це найкоротша відповідь, і вона фіксує питання NPE.
LConrad

-3

NullPointerException - на сьогоднішній день найбільш часто зустрічається виняток (принаймні, в моєму випадку). Щоб уникнути цього, я захищаюся і додаю купу недійсних чеків, і в кінцевому підсумку я здув і потворний код. Java 8 представляє необов'язково обробляти нульові посилання, щоб ви могли визначити нульові та ненульові значення.

З цього приводу я б загорнув усі нульові посилання у контейнер "Необов'язково". Ми також не повинні порушувати відсталу сумісність. Ось код.

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
марна відповідь, чому ви повинні позбутися нуля, щоб виправити це? Це проблема Collectors.toMap()
ненульових

@Enerccio заспокої приятелю !! Покладатися на нульові значення не є хорошою практикою. Якби ви використовували Необов’язково, ви б не стикалися з NPE в першу чергу. Читайте про додаткові звичаї.
TriCore

1
і чому це? Нульове значення нормальне, проблема - недокументована бібліотека. Необов’язково приємно, але не скрізь.
Enerccio
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.