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.

nullsiempre fue un poco problemático, como en TreeMap. Tal vez un buen momento para probarOptional<Boolean>? De lo contrario, divida y use el filtro.nullpodría ser un problema para una clave, pero en este caso es el valor.null,HashMappor ejemplo, pueden tener unanullclave y cualquier número denullvalores, puede intentar crear una personalizadaCollectorutilizando una enHashMaplugar de utilizar la predeterminada.HashMap, como se muestra en la primera línea de stacktrace. El problema no es que un valorMapno puede contenernull, sino que el segundo argumento de laMap#mergefunción no puede ser nulo.Respuestas:
Puede solucionar este error conocido en OpenJDK con esto:
No es tan bonito, pero funciona. Resultado:
( Este tutorial me ayudó más).
fuente
() -> new TreeMap<>(String.CASE_INSENSITIVE_ORDER)crear unaStringclave insensible a mayúsculas y minúsculasTreeMap.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)HashMapy luego llamaputAll()para cada entrada individual. Personalmente, en determinadas circunstancias, elegiría una solución que no sea de flujo, oforEach()si la entrada es paralela.No es posible con los métodos estáticos de
Collectors. El javadoc detoMapexplica quetoMapse basa enMap.merge:y el javadoc de
Map.mergedice:Puede evitar el bucle for utilizando el
forEachmétodo de su lista.pero no es realmente simple que a la antigua usanza:
fuente
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 detoMapsí indican el uso deMap.mergeOP, pero no el que está utilizando.Escribí una
Collectorque, a diferencia de la predeterminada de Java, no se bloquea cuando tienesnullvalores:Simplemente reemplace su
Collectors.toMap()llamada por una llamada a esta función y solucionará el problema.fuente
nullvalores y usarputIfAbsentno juega bien juntos. No detecta claves duplicadas cuando se asignan anull...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:
Y las pruebas usando JUnit y afirm:
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
fuente
(map1, map2) -> { int total = map1.size() + map2.size(); map1.putAll(map2); if(map1.size() < total.size()) throw new IllegalStateException("Duplicate key(s)"); return map1; }accumulator()realidad lo comprueba. Tal vez debería hacer algunas transmisiones paralelas una vez :)Aquí hay un recopilador algo más simple que el propuesto por @EmmanuelTouzery. Úselo si le gusta:
Simplemente lo reemplazamos
nullcon algún objeto personalizadononey hacemos la operación inversa en el finalizador.fuente
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("")))fuente
De acuerdo con la
StacktraceCuando se llama el
map.mergeHará un
nullchequeo como primera cosaNo 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:
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.toMapfuente
He modificado ligeramente la implementación de Emmanuel Touzery .
Esta versión;
Pruebas unitarias:
fuente
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:
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 ponerkel valornull, 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:
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.
fuente
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.fuente
Retener todas las preguntas con identificadores pequeños
fuente
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.
fuente
Collectors.toMap()valores no nulos