Java 8 NullPointerException in Collectors.toMap


331

Java 8 Collectors.toMapgenera a NullPointerExceptionse uno dei valori è 'null'. Non capisco questo comportamento, le mappe possono contenere puntatori null come valore senza problemi. C'è una buona ragione per cui i valori non possono essere nulli per Collectors.toMap?

Inoltre, c'è un buon modo Java 8 per risolvere questo problema o dovrei tornare al vecchio vecchio per loop?

Un esempio del mio problema:

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)

Questo problema esiste ancora in Java 11.


5
nullè sempre stato un po 'problematico, come in TreeMap. Forse un bel momento per provare Optional<Boolean>? Altrimenti dividi e usa il filtro.
Joop Eggen,

5
@JoopEggen nullpotrebbe essere un problema per una chiave, ma in questo caso è il valore.
Gontard,

Non tutte le mappe hanno problemi null, HashMapad esempio possono avere una nullchiave e un numero qualsiasi di nullvalori, puoi provare a creare una personalizzata Collectorusando a HashMapinvece di usare quella predefinita.
Kajacx,

2
@kajacx Ma l'implementazione predefinita è HashMap- come mostrato nella prima riga di stacktrace. Il problema non è che un valore Mapnon può contenere null, ma che il secondo argomento della Map#mergefunzione non può essere nullo.
czerny,

Personalmente, date le circostanze, andrei con una soluzione non stream o forEach () se l'input è parallelo. Le simpatiche soluzioni basate sul flusso breve di seguito potrebbero avere prestazioni terribili.
Ondra Žižka,

Risposte:


302

È possibile aggirare questo bug noto in OpenJDK con questo:

Map<Integer, Boolean> collect = list.stream()
        .collect(HashMap::new, (m,v)->m.put(v.getId(), v.getAnswer()), HashMap::putAll);

Non è molto carino, ma funziona. Risultato:

1: true
2: true
3: null

( questo tutorial mi ha aiutato di più.)


3
@Jagger sì, una definizione di un fornitore (il primo argomento) è una funzione che passa alcun parametro e restituisce un risultato, quindi la lambda per il vostro caso sarebbe () -> new TreeMap<>(String.CASE_INSENSITIVE_ORDER)di creare un case insensitive Stringdigitato TreeMap.
Brett Ryan,

2
Questa è la risposta corretta e IMHO cosa dovrebbe fare JDK per la sua versione predefinita non sovraccaricata. Forse l'unione è più veloce, ma non ho testato.
Brett Ryan,

1
Ho dovuto specificare i parametri di tipo al fine di compilare, in questo modo: Map<Integer, Boolean> collect = list.stream().collect(HashMap<Integer, Boolean>::new, (m,v)->m.put(v.getId(), v.getAnswer()), HashMap<Integer, Boolean>::putAll);. Ho avuto: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
Questo potrebbe essere piuttosto lento su un input di grandi dimensioni. Si crea un HashMape quindi si chiama putAll()per ogni singola voce. Personalmente, in determinate circostanze, andrei con una soluzione non stream o forEach()se l'input è parallelo.
Ondra Žižka,

3
Attenzione che questa soluzione si comporta diversamente dall'implementazione originale di toMap. L'implementazione originale rileva chiavi duplicate e genera una IllegalStatException, ma questa soluzione accetta silenziosamente l'ultima chiave. Soluzione di Emmanuel Touzery ( stackoverflow.com/a/32648397/471214 ) è più vicino al comportamento originale.
mmdemirbas

174

Non è possibile con i metodi statici di Collectors. Il javadoc di toMapspiega che toMapsi basa su Map.merge:

@param mergeFunzione di una funzione di unione, utilizzata per risolvere le collisioni tra valori associati alla stessa chiave, fornita a Map#merge(Object, Object, BiFunction)}

e il javadoc di Map.mergedice:

@throws NullPointerException se la chiave specificata è nulla e questa mappa non supporta chiavi null o il valore o il remapping Funzione è null

Puoi evitare il ciclo for usando il forEachmetodo del tuo elenco.

Map<Integer,  Boolean> answerMap = new HashMap<>();
answerList.forEach((answer) -> answerMap.put(answer.getId(), answer.getAnswer()));

ma non è molto semplice rispetto al vecchio modo:

Map<Integer, Boolean> answerMap = new HashMap<>();
for (Answer answer : answerList) {
    answerMap.put(answer.getId(), answer.getAnswer());
}

3
In tal caso, preferirei usare il vecchio stile per ciascuno. Dovrei considerare questo un bug in toMerge? dato che l'uso di questa funzione di unione è davvero un dettaglio di implementazione, oppure è un buon ragionamento per non consentire a toMap di elaborare valori null?
Jasper,

6
È specificato nel javadoc di merge, ma non è indicato nel documento di toMap
Jasper

119
Non avrei mai pensato che i valori null nella mappa avrebbero avuto un tale impatto sull'API standard, preferirei considerarlo un difetto.
Askar Kalykov,

16
In realtà i documenti API non indicano nulla sull'uso di Map.merge. Questo IMHO è un difetto nell'implementazione che limita un caso d'uso perfettamente accettabile che è stato trascurato. I metodi sovraccarichi di toMapdo indicano l'uso Map.mergema non quello utilizzato dall'OP.
Brett Ryan,

11
@Jasper c'è anche la segnalazione di bug bugs.openjdk.java.net/browse/JDK-8148463
pixel

23

Ho scritto un Collectorche, a differenza di quello predefinito di Java, non si arresta in modo anomalo quando si hanno nullvalori:

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

Basta sostituire la Collectors.toMap()chiamata a una chiamata a questa funzione e risolverà il problema.


1
Ma consentire nullvalori e usare putIfAbsentnon gioca bene insieme. Non rileva chiavi duplicate quando eseguono il mapping a null...
Holger

10

Sì, una mia risposta in ritardo, ma penso che possa aiutare a capire cosa sta succedendo sotto il cofano nel caso in cui qualcuno voglia codificare qualche altra Collectorlogica.

Ho cercato di risolvere il problema codificando un approccio più nativo e diretto. Penso che sia il più diretto possibile:

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

    };
  }

}

E i test con JUnit e 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");
  }

E come lo usi? Bene, basta usarlo invece che toMap()come mostrano i test. Questo rende il codice chiamante il più pulito possibile.

EDIT:
implementata l'idea di Holger di seguito, aggiunto un metodo di prova


1
Il combinatore non verifica la presenza di chiavi duplicate. Se vuoi evitare di controllare ogni chiave, puoi usare qualcosa come(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, è vero. Soprattutto da quando accumulator()effettivamente lo controlla. Forse dovrei fare alcuni flussi paralleli una volta :)
sjngm

7

Ecco un collezionista un po 'più semplice di quello proposto da @EmmanuelTouzery. Usalo se ti piace:

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

Sostituiamo semplicemente nullcon un oggetto personalizzato nonee facciamo l'operazione inversa nella stazione di finitura.


5

Se il valore è una stringa, potrebbe funzionare: map.entrySet().stream().collect(Collectors.toMap(e -> e.getKey(), e -> Optional.ofNullable(e.getValue()).orElse("")))


4
Funziona solo se stai bene con la modifica dei dati. I metodi a valle potrebbero prevedere valori nulli anziché stringhe vuote.
Sam Buchmiller,

3

Secondo il 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)

Quando si chiama il map.merge

        BiConsumer<M, T> accumulator
            = (map, element) -> map.merge(keyMapper.apply(element),
                                          valueMapper.apply(element), mergeFunction);

Farà un nullcontrollo come prima cosa

if (value == null)
    throw new NullPointerException();

Non uso Java 8 così spesso, quindi non so se esiste un modo migliore per risolverlo, ma risolverlo è un po 'difficile.

Potresti fare:

Usa il filtro per filtrare tutti i valori NULL e nel codice Javascript controlla se il server non ha inviato alcuna risposta per questo ID significa che non ha risposto ad esso.

Qualcosa come questo:

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

Oppure usa peek, che viene usato per alterare l'elemento stream per element. Usando peek potresti cambiare la risposta in qualcosa di più accettabile per la mappa, ma significa modificare un po 'la tua logica.

Sembra che se si desidera mantenere il design attuale che si dovrebbe evitare Collectors.toMap


3

Ho leggermente modificato l'implementazione di Emmanuel Touzery .

Questa versione;

  • Consente chiavi null
  • Consente valori null
  • Rileva chiavi duplicate (anche se sono null) e genera IllegalStateException come nell'implementazione JDK originale.
  • Rileva chiavi duplicate anche quando la chiave è già mappata sul valore null. In altre parole, separa una mappatura con valore null da nessuna mappatura.
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 unitari:

@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

Mi dispiace riaprire una vecchia domanda, ma dato che è stato modificato di recente dicendo che il "problema" rimane ancora in Java 11, mi è sembrato di voler sottolineare questo:

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

ti dà l'eccezione puntatore null perché la mappa non consente null come valore. Questo ha senso perché se cerchi la chiave in una mappa ke questa non è presente, il valore restituito è già null(vedi javadoc). Quindi, se tu fossi in grado di inserire kil valore null, la mappa sembrerebbe comportarsi in modo strano.

Come qualcuno ha detto nei commenti, è abbastanza facile risolverlo usando il filtro:

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

in questo modo nessun nullvalore verrà inserito nella mappa e ANCORA otterrai nullcome "valore" quando cerchi un ID che non ha una risposta nella mappa.

Spero che questo abbia senso per tutti.


1
Avrebbe senso se una mappa non consentisse valori nulli, ma lo fa. Puoi fare answerMap.put(4, null);senza problemi. Hai ragione che con la tua soluzione proposta otterrai lo stesso risultato per anserMap.get () se non è presente come se il valore fosse inserito come nullo. Tuttavia, se si scorre su tutte le voci della mappa c'è ovviamente una differenza.
Jasper,

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
voto perché questo compila. La risposta accettata non viene compilata perché Map :: putAll non ha un valore di ritorno.
Taugenichts,

0

Mantenere tutti gli ID delle domande con una piccola modifica

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

Penso che questa sia la risposta migliore: è la risposta più concisa e risolve il problema degli NPE.
LConrad,

-3

NullPointerException è di gran lunga l'eccezione più frequentemente riscontrata (almeno nel mio caso). Per evitare ciò, vado sulla difensiva e aggiungo un mucchio di controlli nulli e finisco per avere un codice gonfio e brutto. Java 8 introduce Opzionale per gestire i riferimenti null in modo da poter definire valori nulla e non nulla.

Detto questo, vorrei avvolgere tutti i riferimenti nullable nel contenitore opzionale. Non dovremmo inoltre interrompere la retrocompatibilità. Ecco il codice

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
risposta inutile, perché dovresti sbarazzarti di null per risolvere questo problema? Questo è un problema di Collectors.toMap()valori non nulli
Enerccio,

@Enerccio calmati amico !! Affidarsi a valori null non è una buona pratica. Se avessi usato Opzionale, non avresti incontrato NPE in primo luogo. Leggi gli usi opzionali.
TriCore,

1
e perché? Il valore nullo va bene, è la libreria non documentata che è il problema. Opzionale è bello ma non ovunque.
Enerccio,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.