¿Cómo debemos administrar el flujo jdk8 para valores nulos?

88

Hola, compañeros desarrolladores de Java:

Sé que el tema puede ser un poco in advanceya que el JDK8 aún no se ha lanzado (y no por ahora de todos modos ...) pero estaba leyendo algunos artículos sobre las expresiones Lambda y particularmente la parte relacionada con la nueva API de colección conocida como Stream.

Aquí está el ejemplo dado en el artículo de la Revista Java (es un algoritmo de población de nutrias ...):

Set<Otter> otters = getOtters();
System.out.println(otters.stream()
    .filter(o -> !o.isWild())
    .map(o -> o.getKeeper())
    .filter(k -> k.isFemale())
    .into(new ArrayList<>())
    .size());

Mi pregunta es ¿qué sucede si en medio de la iteración interna de Set, una de las nutrias es nula?

Esperaría que se lanzara una NullPointerException, pero tal vez todavía estoy atrapado en el paradigma de desarrollo anterior (no funcional), ¿alguien puede aclararme cómo se debe manejar esto?

Si esto realmente arroja una NullPointerException, encuentro la función bastante peligrosa y tendré que usarla solo como se muestra a continuación:

  • Desarrollador para asegurarse de que no haya un valor nulo (tal vez usando un .filter anterior (o -> o! = Null))
  • Desarrollador para garantizar que la aplicación nunca genere una nutria nula o un objeto NullOtter especial con el que lidiar.

¿Cuál es la mejor opción o cualquier otra opción?

¡Gracias!

clemente
fuente
3
Yo diría que depende del programador hacer lo correcto aquí; la JVM y el compilador solo pueden hacer mucho. Sin embargo, tenga en cuenta que algunas implementaciones de colección no permitirán valores nulos.
fge
18
Puede usar filter(Objects::nonNull)con Objectsdesdejava.utils
Benj

Respuestas:

45

El pensamiento actual parece ser "tolerar" nulos, es decir, permitirlos en general, aunque algunas operaciones son menos tolerantes y pueden terminar arrojando NPE. Consulte la discusión sobre nulos en la lista de correo del grupo de expertos de Bibliotecas Lambda, específicamente este mensaje . Posteriormente surgió un consenso en torno a la opción 3 (con una notable objeción de Doug Lea). Así que sí, la preocupación del OP sobre la explosión de oleoductos con NPE es válida.

No en vano Tony Hoare se refirió a los nulos como el "error de mil millones de dólares". Tratar con nulos es un verdadero dolor de cabeza. Incluso con colecciones clásicas (sin considerar lambdas o streams), los nulos son problemáticos. Como mencionó fge en un comentario, algunas colecciones permiten valores nulos y otras no. Con colecciones que permiten valores nulos, esto introduce ambigüedades en la API. Por ejemplo, con Map.get () , un retorno nulo indica que la clave está presente y su valor es nulo o que la clave está ausente. Hay que trabajar más para eliminar la ambigüedad de estos casos.

El uso habitual de nulo es para denotar la ausencia de un valor. El enfoque para lidiar con esto propuesto para Java SE 8 es introducir un nuevo java.util.Optionaltipo, que encapsula la presencia / ausencia de un valor, junto con comportamientos de proporcionar un valor predeterminado, o lanzar una excepción, o llamar a una función, etc. si el valor está ausente. Optionales utilizado solo por nuevas API, sin embargo, todo lo demás en el sistema aún tiene que soportar la posibilidad de nulos.

Mi consejo es evitar las referencias nulas reales en la mayor medida posible. Es difícil ver en el ejemplo dado cómo podría haber una Nutria "nula". Pero si fuera necesario, las sugerencias del OP de filtrar valores nulos o mapearlos a un objeto centinela (el Patrón de objeto nulo ) son buenos enfoques.

Stuart Marks
fuente
3
¿Por qué evitar los nulos? Se utilizan ampliamente en bases de datos.
Raffi Khatchadourian
5
@RaffiKhatchadourian Sí, los nulos se utilizan en las bases de datos, pero son igualmente problemáticos. Vea esto y esto y lea todas las respuestas y comentarios. También considere el impacto de SQL null en expresiones booleanas: en.wikipedia.org/wiki/… ... esta es una rica fuente de errores de consulta.
Stuart Marks
1
@RaffiKhatchadourian Porque nullapesta, por eso. Acc. para una encuesta que vi (no puedo encontrarla), NPE es la excepción número 1 en Java. SQL no es un lenguaje de programación de alto nivel, ciertamente no es funcional, que desprecia el nulo, así que no le importa.
Abhijit Sarkar
91

Aunque las respuestas son 100% correctas, una pequeña sugerencia para mejorar el nullmanejo de casos de la propia lista con Opcional :

 List<String> listOfStuffFiltered = Optional.ofNullable(listOfStuff)
                .orElseGet(Collections::emptyList)
                .stream()
                .filter(Objects::nonNull)
                .collect(Collectors.toList());

La parte Optional.ofNullable(listOfStuff).orElseGet(Collections::emptyList)le permitirá manejar bien el caso cuando listOfStuffsea ​​nulo y devolver una lista vacía en lugar de fallar con NullPointerException.

Johnny
fuente
2
Me gusta esto, evita la comprobación explícita de nulos.
Chris
1
esto se ve mejor claro ... agradable y exactamente lo que necesitaba
Abdullah Al Noman
Está comprobando explícitamente si hay nulo, solo que de una manera diferente. Si se permite que sea nulo, debería ser un Opcional, esa es la idea, ¿verdad? Prohibir todos los valores nulos con opcionales. Además, es una mala práctica devolver nulo en lugar de una lista vacía, por lo que muestra dos olores de código si veo esto: no se utilizan opcionales y no se devuelven flujos vacíos. Y este último está disponible por más de 20 años, por lo que es maduro ...
Koos Gadellaa
¿Qué pasa si quiero devolver una lista nula en lugar de vacía?
Ashburn RK
@AshburnRK es una mala práctica. Debería devolver una lista vacía.
Johnny
69

La respuesta de Stuart proporciona una gran explicación, pero me gustaría dar otro ejemplo.

Me encontré con este problema al intentar realizar una transmisión reduceen un flujo que contiene valores nulos (en realidad lo fue LongStream.average(), que es un tipo de reducción). Dado que average () devuelve OptionalDouble, asumí que Stream podría contener nulos, pero en su lugar se lanzó una NullPointerException. Esto se debe a la explicación de Stuart de nulo versus vacío.

Entonces, como sugiere el OP, agregué un filtro así:

list.stream()
    .filter(o -> o != null)
    .reduce(..);

O, como se señala a continuación, use el predicado proporcionado por la API de Java:

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

De la discusión de la lista de correo, Stuart vinculó: Brian Goetz sobre nulos en Streams

bparry
fuente
19

Si solo desea filtrar los valores nulos de una secuencia, simplemente puede usar una referencia de método a java.util.Objects.nonNull (Object) . De su documentación:

Este método existe para ser utilizado como predicado ,filter(Objects::nonNull)

Por ejemplo:

List<String> list = Arrays.asList( null, "Foo", null, "Bar", null, null);

list.stream()
    .filter( Objects::nonNull )  // <-- Filter out null values
    .forEach( System.out::println );

Esto imprimirá:

Foo
Bar
Andy Thomas
fuente
7

Un ejemplo de cómo evitar nulos, por ejemplo, use el filtro antes de agrupar

Filtre las instancias nulas antes de groupingBy.

Aquí hay un ejemplo

MyObjectlist.stream()
            .filter(p -> p.getSomeInstance() != null)
            .collect(Collectors.groupingBy(MyObject::getSomeInstance));
Ilan M
fuente