Java 8 NullPointerException en Collectors.toMap

331

Java 8 Collectors.toMaparroja un NullPointerExceptionsi uno de los valores es 'nulo'. No entiendo este comportamiento, los mapas pueden contener punteros nulos como valor sin ningún problema. ¿Hay una buena razón por la cual los valores no pueden ser nulos Collectors.toMap?

Además, ¿hay una buena forma de Java 8 para solucionar esto, o debería volver al ciclo anterior para el bucle?

Un ejemplo de mi 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)

Este problema todavía existe en Java 11.

Jaspe
fuente
55
nullsiempre fue un poco problemático, como en TreeMap. Tal vez un buen momento para probar Optional<Boolean>? De lo contrario, divida y use el filtro.
Joop Eggen
55
@JoopEggen nullpodría ser un problema para una clave, pero en este caso es el valor.
gontard
No todos los mapas tienen problemas null, HashMappor ejemplo, pueden tener una nullclave y cualquier número de nullvalores, puede intentar crear una personalizada Collectorutilizando una en HashMaplugar de utilizar la predeterminada.
kajacx
2
@kajacx Pero la implementación predeterminada es HashMap, como se muestra en la primera línea de stacktrace. El problema no es que un valor Mapno puede contener null, sino que el segundo argumento de la Map#mergefunción no puede ser nulo.
czerny
Personalmente, con las circunstancias dadas, elegiría una solución que no sea de flujo o forEach () si la entrada es paralela. Las buenas soluciones basadas en flujo corto a continuación podrían tener un rendimiento terrible.
Ondra Žižka

Respuestas:

302

Puede solucionar este error conocido en OpenJDK con esto:

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

No es tan bonito, pero funciona. Resultado:

1: true
2: true
3: null

( Este tutorial me ayudó más).

kajacx
fuente
3
@Jagger sí, una definición de proveedor (el primer argumento) es una función que no pasa parámetros y devuelve un resultado, por lo tanto, la lambda para su caso sería () -> new TreeMap<>(String.CASE_INSENSITIVE_ORDER)crear una Stringclave insensible a mayúsculas y minúsculas TreeMap.
Brett Ryan
2
Esta es la respuesta correcta, y en mi humilde opinión, lo que el JDK debería estar haciendo para su versión predeterminada no sobrecargada en su lugar. Quizás fusionar es más rápido, pero no lo he probado.
Brett Ryan
1
Tenía que especifique los parámetros de tipo con el fin de recopilar, de esa manera: Map<Integer, Boolean> collect = list.stream().collect(HashMap<Integer, Boolean>::new, (m,v)->m.put(v.getId(), v.getAnswer()), HashMap<Integer, Boolean>::putAll);. Tuve: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
Esto podría ser bastante lento en una entrada grande. Usted crea un HashMapy luego llama putAll()para cada entrada individual. Personalmente, en determinadas circunstancias, elegiría una solución que no sea de flujo, o forEach()si la entrada es paralela.
Ondra Žižka
3
Tenga en cuenta que esta solución se comporta de manera diferente a la implementación original de toMap. La implementación original detecta claves duplicadas y genera una IllegalStatException, pero esta solución acepta silenciosamente la última clave. La solución de Emmanuel Touzery ( stackoverflow.com/a/32648397/471214 ) está más cerca del comportamiento original.
mmdemirbas
174

No es posible con los métodos estáticos de Collectors. El javadoc de toMapexplica que toMapse basa en Map.merge:

@param mergeFunction una función de combinación, utilizada para resolver colisiones entre valores asociados con la misma clave, tal como se proporciona a Map#merge(Object, Object, BiFunction)}

y el javadoc de Map.mergedice:

@throws NullPointerException si la clave especificada es nula y este mapa no admite claves nulas o el valor o remappingFunction es nulo

Puede evitar el bucle for utilizando el forEachmétodo de su lista.

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

pero no es realmente simple que a la antigua usanza:

Map<Integer, Boolean> answerMap = new HashMap<>();
for (Answer answer : answerList) {
    answerMap.put(answer.getId(), answer.getAnswer());
}
gontard
fuente
3
En ese caso, preferiría usar el anticuado para cada uno. ¿Debería considerar esto un error en toMerge? como el uso de esta función de fusión es realmente un detalle de implementación, o es un buen razonamiento para no permitir que toMap procese valores nulos?
Jasper
66
Se especifica en el javadoc de fusión, pero no se menciona en el documento de toMap
Jasper
119
Nunca pensé que los valores nulos en el mapa tendrían un impacto tan grande en la API estándar, prefiero considerarlo como un defecto.
Askar Kalykov
16
En realidad, los documentos API no indican nada sobre el uso de Map.merge. Este en mi humilde opinión es un defecto en la implementación que restringe un caso de uso perfectamente aceptable que se ha pasado por alto. Los métodos sobrecargados de toMapsí indican el uso de Map.mergeOP, pero no el que está utilizando.
Brett Ryan
11
@Jasper incluso hay informes de errores bugs.openjdk.java.net/browse/JDK-8148463
pixel
23

Escribí una Collectorque, a diferencia de la predeterminada de Java, no se bloquea cuando tienes nullvalores:

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

Simplemente reemplace su Collectors.toMap()llamada por una llamada a esta función y solucionará el problema.

Emmanuel Touzery
fuente
1
Pero permitir nullvalores y usar putIfAbsentno juega bien juntos. No detecta claves duplicadas cuando se asignan a null...
Holger
10

Sí, una respuesta tardía de mi parte, pero creo que puede ser útil entender lo que sucede debajo del capó en caso de que alguien quiera codificar alguna otra Collectorlógica.

Traté de resolver el problema codificando un enfoque más nativo y directo. Creo que es lo más directo posible:

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

    };
  }

}

Y las pruebas usando JUnit y afirm:

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

y como lo usas? Bueno, solo úsalo en lugar de toMap()como lo muestran las pruebas. Esto hace que el código de llamada se vea lo más limpio posible.

EDITAR:
implementó la idea de Holger a continuación, agregó un método de prueba

sjngm
fuente
1
El combinador no busca claves duplicadas. Si desea evitar verificar cada clave, puede usar algo como(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 Sí, eso es cierto. Sobre todo porque en accumulator()realidad lo comprueba. Tal vez debería hacer algunas transmisiones paralelas una vez :)
sjngm
7

Aquí hay un recopilador algo más simple que el propuesto por @EmmanuelTouzery. Úselo si le gusta:

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

Simplemente lo reemplazamos nullcon algún objeto personalizado noney hacemos la operación inversa en el finalizador.

Tagir Valeev
fuente
5

Si el valor es una Cadena, esto podría funcionar: map.entrySet().stream().collect(Collectors.toMap(e -> e.getKey(), e -> Optional.ofNullable(e.getValue()).orElse("")))

Gnana
fuente
44
Eso funciona solo si está de acuerdo con modificar los datos. Los métodos posteriores pueden esperar valores nulos en lugar de cadenas vacías.
Sam Buchmiller
3

De acuerdo con la 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)

Cuando se llama el map.merge

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

Hará un nullchequeo como primera cosa

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

No uso Java 8 con tanta frecuencia, así que no sé si hay una mejor manera de solucionarlo, pero solucionarlo es un poco difícil.

Podrías hacerlo:

Use filter para filtrar todos los valores NULL, y en el código Javascript verifique si el servidor no envió ninguna respuesta para esta identificación, lo que significa que no respondió.

Algo como esto:

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

O use peek, que se usa para alterar el elemento stream por elemento. Usando peek, podría cambiar la respuesta a algo más aceptable para el mapa, pero significa editar su lógica un poco.

Parece que si desea mantener el diseño actual, debe evitar Collectors.toMap

Marco Acierno
fuente
3

He modificado ligeramente la implementación de Emmanuel Touzery .

Esta versión;

  • Permite claves nulas
  • Permite valores nulos
  • Detecta claves duplicadas (incluso si son nulas) y lanza IllegalStateException como en la implementación original de JDK.
  • Detecta claves duplicadas también cuando la clave ya está asignada al valor nulo. En otras palabras, separa una asignación con valor nulo de la no asignación.
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;
        }
    );
}

Pruebas unitarias:

@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))
    );
}
mmdemirbas
fuente
1

Lamento volver a abrir una pregunta anterior, pero como se editó recientemente diciendo que el "problema" aún permanece en Java 11, sentí que quería señalar esto:

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

le da la excepción de puntero nulo porque el mapa no permite un valor nulo. Esto tiene sentido porque si busca en un mapa la clave ky no está presente, entonces el valor devuelto ya está null(ver javadoc). Entonces, si pudieras poner kel valor null, el mapa parecería que se comporta de manera extraña.

Como alguien dijo en los comentarios, es bastante fácil resolver esto mediante el filtrado:

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

de esta manera no nullse insertarán valores en el mapa, y TODAVÍA se obtendrá nullcomo el "valor" cuando se busca una identificación que no tiene una respuesta en el mapa.

Espero que esto tenga sentido para todos.

Luca
fuente
1
Tendría sentido si un mapa no permitiera valores nulos, pero lo hace. Puedes hacerlo answerMap.put(4, null);sin ningún problema. Tiene razón en que con su solución propuesta obtendrá el mismo resultado para anserMap.get () si no está presente como si el valor se insertara como nulo. Sin embargo, si itera sobre todas las entradas del mapa, obviamente hay una diferencia.
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());
}
Igor Zubchenok
fuente
1
votación porque esto compila. La respuesta aceptada no se compila porque Map :: putAll no tiene un valor de retorno.
Taugenichts
0

Retener todas las preguntas con identificadores pequeños

Map<Integer, Boolean> answerMap = 
  answerList.stream()
            .collect(Collectors.toMap(Answer::getId, a -> 
                       Boolean.TRUE.equals(a.getAnswer())));
sigirisetti
fuente
Creo que esta es la mejor respuesta: es la respuesta más concisa y soluciona el problema de NPE.
LConrad
-3

NullPointerException es, con mucho, la excepción más frecuente (al menos en mi caso). Para evitar esto, me pongo a la defensiva y agrego un montón de controles nulos y termino teniendo un código hinchado y feo. Java 8 presenta Opcional para manejar referencias nulas para que pueda definir valores anulables y no anulables.

Dicho esto, envolvería todas las referencias anulables en el contenedor opcional. Tampoco debemos romper la compatibilidad con versiones anteriores. Aquí está el código.

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);
    }
}
TriCore
fuente
1
respuesta inútil, ¿por qué debería deshacerse de nulo para arreglar esto? Este es un problema de Collectors.toMap()valores no nulos
Enerccio
@Enerccio cálmate amigo !! Confiar en valores nulos no es una buena práctica. Si hubiera utilizado Opcional, no habría encontrado NPE en primer lugar. Lea sobre usos opcionales.
TriCore
1
¿Y por qué es eso? El valor nulo está bien, el problema es la biblioteca no documentada. Opcional es agradable pero no en todas partes.
Enerccio