Filtre valores apenas se não for nulo usando lambda no Java8

160

Eu tenho uma lista de objetos dizem car. Eu quero filtrar esta lista com base em algum parâmetro usando Java 8. Mas se o parâmetro for null, ele lança NullPointerException. Como filtrar valores nulos?

O código atual é o seguinte

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

Isso joga NullPointerExceptionse getName()retorna null.

vaibhavvc1092
fonte
Deseja "filtrar valores apenas se não for nulo" ou "filtrar valores nulos"? Isso parece contraditório para mim.
Holger
3
Posso sugerir que você aceite a resposta de Tunaki, pois parece ser a única que realmente responde à sua pergunta.
Mark Booth

Respostas:

322

Neste exemplo em particular, acho que o @Tagir está 100% correto, coloque-o em um filtro e faça as duas verificações. Eu não usaria Optional.ofNullableo material opcional é realmente para os tipos de retorno não estarem fazendo lógica ... mas realmente nem aqui nem ali.

Queria ressaltar que java.util.Objectsexiste um método legal para isso em um caso amplo, para que você possa fazer isso:

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

O que limpará seus objetos nulos. Para quem não conhece, essa é a abreviação para o seguinte:

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

Para responder parcialmente à pergunta em questão e retornar a lista de nomes de carros que começa com "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());

Depois de se acostumar com as lambdas de taquigrafia, você também pode fazer isso:

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());

Infelizmente, uma vez que .map(Car::getName)você retornará apenas a lista de nomes, não os carros. Tão menos bonita, mas responde totalmente à pergunta:

cars.stream()
    .filter(car -> Objects.nonNull(car))
    .filter(car -> Objects.nonNull(car.getName()))
    .filter(car -> car.getName().startsWith("M"))
    .collect(Collectors.toList());
xbakesx
fonte
1
observe que o carro nulo não é o problema. Nesse caso, é a propriedade name causando problemas. Portanto, Objects::nonNullnão pode ser usada aqui, e no último conselho que deveria ser cars.stream() .filter(car -> Objects.nonNull(car.getName()))eu acredito
kiedysktos
1
BTW, eu acho que cars.stream() .filter(car -> Objects.nonNull(car.getName()) && car.getName().startsWith("M"))seria o resumo do seu conselho neste contexto questão
kiedysktos
3
@kiedysktos Esse é um bom argumento de que a chamada .startWithtambém pode causar um ponteiro nulo. O ponto que eu estava tentando destacar é que o Java fornece um método especificamente para filtrar objetos nulos de seus fluxos.
xbakesx
@ Mark Booth sim, obviamente, Objects.nonNullé equivalente a != null, sua opção é mais curtos
kiedysktos
1
Você não está criando uma lista de nomes de carros ( String) em vez de carros ( Car)?
user1803551
59

Você só precisa filtrar os carros que têm um nullnome:

requiredCars = cars.stream()
                   .filter(c -> c.getName() != null)
                   .filter(c -> c.getName().startsWith("M"));
Tunaki
fonte
3
É uma pena que esta resposta não seja mais votada, pois parece ser a única resposta que realmente responde à pergunta.
Mark Booth
@MarkBooth A pergunta "Como filtrar valores nulos?" parece ser bem respondida por xbakesx.
precisa
@MarkBooth Olhando para as datas corretas. Meu erro.
vegemite4me
Em termos de desempenho, é bom filtrar o fluxo duas vezes ou melhor usar o predicado para a filtragem? Só quero saber.
Vaibhav_Sharma
51

As respostas propostas são ótimas. Gostaria apenas de sugerir uma melhoria para lidar com o caso da lista nula usando Optional.ofNullable, novo recurso no Java 8 :

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

Portanto, a resposta 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
fonte
5
Mau uso de opcional. null nunca deve ser usado como sinônimo de uma coleção vazia em primeiro lugar.
VGR 12/12
4
@ VGR Claro, mas não é isso que acontece na prática. Às vezes (na maioria das vezes) você precisa trabalhar com código no qual muitas pessoas trabalharam. Às vezes, você recebe seus dados de interfaces externas. Para todos esses casos, o opcional é um ótimo uso.
12136 Johnny
1
observe que o carro nulo não é o problema. Nesse caso, é a propriedade name causando problemas. Portanto Objects::nonNull, não resolve o problema, já que o carro não nulo pode ter o nome == null
kiedysktos
Claro @kiedysktos, mas não era isso que eu queria mostrar na resposta. Mas, eu estou aceitando o que você está dizendo e editar a resposta :)
Johnny
24

Você pode fazer isso na etapa de filtro único:

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

Se você não quiser ligar getName()várias vezes (por exemplo, é uma ligação cara), faça o seguinte:

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

Ou de maneira mais sofisticada:

requiredCars = cars.stream().filter(c -> 
    Optional.ofNullable(c.getName()).filter(name -> name.startsWith("M")).isPresent());
Tagir Valeev
fonte
A expansão embutida no segundo exemplo foi valiosa para o meu caso de uso
Paul
3

Aproveitando o 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
fonte
1

você pode usar isso

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