¿Por qué findFirst () lanza una NullPointerException si el primer elemento que encuentra es nulo?

¿Por qué arroja una java.lang.NullPointerException ?

 List strings = new ArrayList(); strings.add(null); strings.add("test"); String firstString = strings.stream() .findFirst() // Exception thrown here .orElse("StringWhenListIsEmpty"); //.orElse(null); // Changing the `orElse()` to avoid ambiguity 

El primer elemento en strings es null , que es un valor perfectamente aceptable. Además, findFirst() devuelve un Opcional , lo que tiene aún más sentido para findFirst() para poder manejar null s.

EDITAR: actualizó el orElse() para que sea menos ambiguo.

La razón de esto es el uso de Optional en la statement. Opcional no puede contener null . Esencialmente, no ofrece ninguna manera de distinguir situaciones “no está allí” y “está ahí, pero está configurado como null “.

Es por eso que la documentación prohíbe explícitamente la situación cuando se selecciona null en findFirst() :

Lanza:

NullPointerException : si el elemento seleccionado es null

Como ya se discutió , los diseñadores de API no suponen que el desarrollador quiera tratar valores null y valores ausentes de la misma manera.

Si aún desea hacer eso, puede hacerlo explícitamente aplicando la secuencia

 .map(Optional::ofNullable).findFirst().flatMap(Function.identity()) 

a la stream. El resultado será un vacío opcional en ambos casos, si no hay primer elemento o si el primer elemento es null . Entonces en tu caso, puedes usar

 String firstString = strings.stream() .map(Optional::ofNullable).findFirst().flatMap(Function.identity()) .orElse(null); 

para obtener un valor null si el primer elemento está ausente o null .

Si desea distinguir entre estos casos, simplemente puede omitir el paso de flatMap :

 Optional firstString = strings.stream() .map(Optional::ofNullable).findFirst().orElse(null); System.out.println(firstString==null? "no such element": firstString.orElse("first element is null")); 

Esto no es muy diferente a su pregunta actualizada. Solo tiene que reemplazar "no such element" con "StringWhenListIsEmpty" y "first element is null" con null . Pero si no te gustan los condicionales, puedes lograrlo también como:

 String firstString = strings.stream().skip(0) .map(Optional::ofNullable).findFirst() .orElseGet(()->Optional.of("StringWhenListIsEmpty")) .orElse(null); 

Ahora, firstString será null si existe un elemento pero es null y será "StringWhenListIsEmpty" cuando no exista ningún elemento.

El siguiente código reemplaza findFirst() con limit(1) y reemplaza orElse() con reduce() :

 String firstString = strings. stream(). limit(1). reduce("StringWhenListIsEmpty", (first, second) -> second); 

limit() permite reduce solo 1 elemento. El BinaryOperator pasó para reduce devoluciones de 1 elemento o de lo contrario "StringWhenListIsEmpty" si ningún elemento llega al "StringWhenListIsEmpty" .

La belleza de esta solución es que no se asigna Optional y BinaryOperator lambda no va a asignar nada.

Puede usar java.util.Objects.nonNull para filtrar la lista antes de encontrar

algo como

 list.stream().filter(Objects::nonNull).findFirst(); 

Opcional se supone que es un tipo de “valor”. (lea la letra pequeña en javadoc 🙂 JVM incluso podría reemplazar todo el Optional con solo Foo , eliminando todos los costos de boxeo y desembalaje. Un Foo null significa un Optional vacío.

Es un diseño posible para permitir Opcional con valor nulo, sin agregar un indicador booleano, simplemente agregue un objeto centinela. (incluso podría usar this como centinela; ver Throwable.cause)

La decisión de que Opcional no pueda incluir null no se basa en el costo del tiempo de ejecución. Este fue un problema enormemente disputado y necesita cavar las listas de correo. La decisión no es convincente para todos.

En cualquier caso, dado que Optional no puede ajustar el valor nulo, nos empuja a una esquina en casos como findFirst . Deben haber razonado que los valores nulos son muy raros (incluso se consideró que Stream debería bloquear los valores nulos), por lo tanto, es más conveniente lanzar una excepción en los valores nulos en lugar de en las secuencias vacías.

Una solución alternativa es box null , por ejemplo

 class Box static Box of(T value){ .. } Optional> first = stream.map(Box::of).findFirst(); 

(Dicen que la solución a cada problema OOP es introducir otro tipo 🙂