Filtre los valores solo si no son nulos usando lambda en Java8

160

Tengo una lista de objetos que decir car. Quiero filtrar esta lista basada en algún parámetro usando Java 8. Pero si el parámetro es null, arroja NullPointerException. ¿Cómo filtrar valores nulos?

El código actual es el siguiente

requiredCars = cars.stream().filter(c -> c.getName().startsWith("M"));

Esto arroja NullPointerExceptionsi getName()regresa null.

vaibhavvc1092
fuente
¿Desea "filtrar valores solo si no son nulos" o "filtrar valores nulos"? Eso me suena contradictorio.
Holger
3
¿Puedo sugerirle que acepte la respuesta de Tunaki, ya que parece ser la única que realmente responde a su pregunta?
Mark Booth

Respuestas:

322

En este ejemplo en particular, creo que @Tagir es 100% correcto, mételo en un filtro y realiza las dos comprobaciones. No usariaOptional.ofNullable las cosas opcionales para que los tipos de retorno no estén haciendo lógica ... pero realmente ni aquí ni allá.

Quería señalar que java.util.Objectstiene un buen método para esto en un caso amplio, por lo que puede hacer esto:

cars.stream()
    .filter(Objects::nonNull)

Lo que limpiará tus objetos nulos. Para cualquiera que no esté familiarizado, esa es la abreviatura de lo siguiente:

cars.stream()
    .filter(car -> Objects.nonNull(car))

Para responder parcialmente la pregunta en cuestión y devolver la lista de nombres de automóviles que comienza con "M":

cars.stream()
    .filter(car -> Objects.nonNull(car))
    .map(car -> car.getName())
    .filter(carName -> Objects.nonNull(carName))
    .filter(carName -> carName.startsWith("M"))
    .collect(Collectors.toList());

Una vez que te acostumbres a las taquillas lambdas, también puedes hacer esto:

cars.stream()
    .filter(Objects::nonNull)
    .map(Car::getName)        // Assume the class name for car is Car
    .filter(Objects::nonNull)
    .filter(carName -> carName.startsWith("M"))
    .collect(Collectors.toList());

Desafortunadamente, una vez .map(Car::getName)que solo regreses la lista de nombres, no los autos. Por lo tanto, menos hermoso pero responde completamente la pregunta:

cars.stream()
    .filter(car -> Objects.nonNull(car))
    .filter(car -> Objects.nonNull(car.getName()))
    .filter(car -> car.getName().startsWith("M"))
    .collect(Collectors.toList());
xbakesx
fuente
1
Tenga en cuenta que el automóvil nulo no es el problema. En este caso, su propiedad de nombre causa problemas. Entonces Objects::nonNullno se puede usar aquí, y en el último consejo debería sercars.stream() .filter(car -> Objects.nonNull(car.getName())) yo creo
kiedysktos
1
Por cierto, creo cars.stream() .filter(car -> Objects.nonNull(car.getName()) && car.getName().startsWith("M"))que sería el resumen de su consejo en este contexto de pregunta
kiedysktos
3
@kiedysktos Ese es un buen punto de que llamar .startWithtambién podría causar un puntero nulo. El punto que estaba tratando de hacer es que Java proporciona un método específicamente para filtrar objetos nulos de sus secuencias.
xbakesx
@Mark Booth sí, obviamente Objects.nonNulles equivalente a != null, su opción es más corta
kiedysktos
1
¿No estás creando una lista de nombres de autos ( String) en lugar de autos ( Car)?
user1803551
59

Solo necesita filtrar los autos que tienen un nullnombre:

requiredCars = cars.stream()
                   .filter(c -> c.getName() != null)
                   .filter(c -> c.getName().startsWith("M"));
Tunaki
fuente
3
Es una verdadera pena que esta respuesta no sea más votada, ya que parece ser la única respuesta que realmente responde a la pregunta.
Mark Booth
@ MarkBooth La pregunta "¿Cómo filtrar valores nulos?" parece haber sido respondido bien por xbakesx.
vegemite4me 01 de
@ MarkBooth Mirando las fechas en que está correcto. Mi error.
vegemite4me 01 de
En cuanto al rendimiento, ¿es bueno filtrar la transmisión dos veces o mejor usar el predicado para filtrar? Solo quiero saber.
Vaibhav_Sharma
51

Las respuestas propuestas son geniales. Sólo quisiera sugerir una mejora para manejar el caso de la lista nula usando Optional.ofNullable, nueva característica en Java 8 :

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

Entonces, la respuesta completa será:

 List<String> carsFiltered = Optional.ofNullable(cars)
                .orElseGet(Collections::emptyList)
                .stream()
                .filter(Objects::nonNull) //filtering car object that are null
                .map(Car::getName) //now it's a stream of Strings
                .filter(Objects::nonNull) //filtering null in Strings
                .filter(name -> name.startsWith("M"))
                .collect(Collectors.toList()); //back to List of Strings
Johnny
fuente
55
Mal uso de Opcional. null nunca debe usarse como sinónimo de una colección vacía en primer lugar.
VGR
44
@VGR Por supuesto, pero eso no es lo que sucede en la práctica. A veces (la mayoría de las veces) necesita trabajar con código en el que mucha gente trabajó. En algún momento recibe sus datos de interfaces externas. Para todos esos casos, Opcional es un gran uso.
Johnny
1
Tenga en cuenta que el automóvil nulo no es el problema. En este caso, su propiedad de nombre causa problemas. Por Objects::nonNulllo tanto , no resuelve el problema ya que el automóvil no nulo puede tener nombre == nulo
kiedysktos
Por supuesto @kiedysktos, pero eso no es lo que quería mostrar en la respuesta. Pero, acepto lo que dices y edito la respuesta :)
Johnny
24

Puede hacer esto en un solo paso de filtro:

requiredCars = cars.stream().filter(c -> c.getName() != null && c.getName().startsWith("M"));

Si no desea llamar getName()varias veces (por ejemplo, es una llamada costosa), puede hacer esto:

requiredCars = cars.stream().filter(c -> {
    String name = c.getName();
    return name != null && name.startsWith("M");
});

O de manera más sofisticada:

requiredCars = cars.stream().filter(c -> 
    Optional.ofNullable(c.getName()).filter(name -> name.startsWith("M")).isPresent());
Tagir Valeev
fuente
La expansión en línea en el segundo ejemplo fue valiosa para mi caso de uso
Paul
3

Aprovechando el poder de java.util.Optional#map():

List<Car> requiredCars = cars.stream()
  .filter (car -> 
    Optional.ofNullable(car)
      .map(Car::getName)
      .map(name -> name.startsWith("M"))
      .orElse(false) // what to do if either car or getName() yields null? false will filter out the element
    )
  .collect(Collectors.toList())
;
rslemos
fuente
1

puedes usar esto

List<Car> requiredCars = cars.stream()
    .filter (t->  t!= null && StringUtils.startsWith(t.getName(),"M"))
    .collect(Collectors.toList());
riverfan
fuente