first java nullpointerexception java-8 java-stream optional

java stream find first match



java 8 findFirst vs map on optional (4)

Dado este código:

class Foo { Integer attr; public Integer getAttr() {return attr;} } List<Foo> list = new ArrayList<>(); list.add(new Foo()); list.stream().map(Foo::getAttr).findAny().orElse(null); //A list.stream().findAny().map(Foo::getAttr).orElse(null); //B

La línea A arroja

java.lang.NullPointerException: nulo

mientras que la línea B devuelve nulo.

¿Cuál es la causa de este comportamiento? Ambos findAny() y map() devuelven Optional<T> .


En primer lugar, los dos fragmentos de código de código son diferentes operaciones:

// v--- stream intermediate operation list.stream().map(Foo::getAttr).findAny().orElse(null); //A // v---- a Optional utility method list.stream().findAny().map(Foo::getAttr).orElse(null); //B

y la excepción NullPointerException ocurre en Stream # findAny operation, ya que no puede aceptar un valor null . debido a que usa Optional.of en vez de Optional.ofNullable . y la documentación de Stream # findAny ya afirma:

Lanza :

NullPointerException : si el elemento seleccionado es nulo

así que si quiere que su fragmento de código A funcione correctamente , debe filtrar todos null valores null antes de llamar a Stream # findAny , por ejemplo:

//when no elements in stream, `findAny` will be return a empty by Optional.empty() // v list.stream().map(Foo::getAttr).filter(Objects::nonNull).findAny().orElse(null);//A


list.stream().map(Foo::getAttr).findAny().orElse(null);

El documento Java para transmisiones dice que Stream: "devuelve una secuencia que consiste en los resultados de aplicar la función dada a los elementos de esta secuencia", y findAny () "podría devolver una excepciónNullPointerException, si el elemento seleccionado es nulo". En su clase Foo, entero (no int) por defecto se establece en nulo porque está declarado pero no inicializado. ver Primitivos ver valores predeterminados e inicialización de objetos en Java

La inicialización es diferente para: A) Miembros de clase (Objetos y primitivos) B) Variables locales


list.stream().map(Foo::getAttr)

... devuelve una secuencia con un elemento, con un valor de nulo.

El findAny() para findAny() (y findFirst() ) dice:

Devoluciones:

un Opcional que describe algún elemento de esta secuencia, o un Opcional vacío si la secuencia está vacía

Lanza:

NullPointerException: si el elemento seleccionado es nulo

Entonces findAny() está haciendo exactamente lo que está documentado: está seleccionando un nulo y, como resultado, está lanzando NullPointerException .

Esto tiene sentido porque Optional es (de nuevo según JavaDoc, pero el énfasis es mío):

Un objeto contenedor que puede contener o no un valor no nulo

... lo que significa que puede garantizar que Optional.ifPresent( x -> x.method()) nunca lanzará NullPointerException debido a que x es nulo.

Entonces findAny() no pudo devolver Optional.of(null) . Y Optional.empty() significa que la transmisión estaba vacía, no que haya encontrado un nulo.

Muchas partes de la infraestructura Stream / Optional tratan de desalentar el uso de nulos.

Puede solucionar esto asignando los nulos a Optionals , para obtener un Optional<Optional<Foo>> , que se ve un poco enrevesado, pero es una representación precisa de su dominio. Optional.empty() significa que la transmisión estaba vacía. Optional.of(Optional.empty()) significa que encontró un elemento nulo:

list.stream().map(Foo::getAttr).map(Optional::ofNullable).findAny()


Bueno, es obviamente debido al orden en el que se realizan estas operaciones y también porque findAny explícitamente dice: throws NullPointerException if the element selected is null

Cuando hace un map(Foo::getAttr) efectivamente ha mapeado eso a null , entonces su Stream ahora contiene un null ; así findAny rompe con una excepción (ya que findAny se aplica en ese nulo)

La otra operación primero encuentra el objeto Foo , luego lo mapea a Foo::getAttr ( Foo::getAttr así a Optional.empty() ), por orElse se llama a orElse .

Además, esto tendría más sentido (al menos para mí):

list.stream() .findAny() .flatMap(f -> Optional.ofNullable(f.getAttr())) .orElse(null);

flatMap correlacionaría con Optional<Integer> flatMap Optional<Integer> (atributos) Optional<Integer> , en caso de que este esté empty obtenga el resultado orElse .