Filtra i valori solo se non nulli utilizzando lambda in Java8


161

Ho un elenco di oggetti dire car. Voglio filtrare questo elenco in base ad alcuni parametri usando Java 8. Ma se il parametro è null, genera NullPointerException. Come filtrare i valori null?

Il codice corrente è il seguente

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

Questo genera NullPointerExceptionse getName()ritorna null.


Vuoi "filtrare i valori solo se non null" o "filtrare i valori null"? Mi sembra contraddittorio.
Holger

3
Potrei suggerire di accettare la risposta di Tunaki in quanto sembra essere l'unica che effettivamente risponde alla tua domanda.
Mark Booth,

Risposte:


323

In questo esempio particolare penso che @Tagir sia corretto al 100%, inseriscilo in un filtro e fai i due controlli. Non Optional.ofNullableuserei le cose opzionali è davvero per i tipi di restituzione di non fare la logica ... ma davvero né qui né lì.

Volevo sottolineare che java.util.Objectsha un buon metodo per questo in un caso ampio, quindi puoi farlo:

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

Che cancellerà i tuoi oggetti null. Per chiunque non abbia familiarità, questa è la scorciatoia per quanto segue:

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

Per rispondere parzialmente alla domanda a portata di mano per restituire l'elenco dei nomi di auto che inizia 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 volta che ti sei abituato alla stenografia lambdas puoi anche fare questo:

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

Sfortunatamente, una volta che .map(Car::getName)hai restituito solo l'elenco dei nomi, non le macchine. Quindi meno bello ma risponde pienamente alla domanda:

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

1
nota che l'auto nulla non è il problema. In questo caso, è la proprietà name che causa problemi. Quindi Objects::nonNullnon può essere usato qui, e nell'ultimo consiglio dovrei essere cars.stream() .filter(car -> Objects.nonNull(car.getName()))io credo
kiedysktos

1
A proposito, penso che cars.stream() .filter(car -> Objects.nonNull(car.getName()) && car.getName().startsWith("M"))sarebbe il riassunto dei tuoi consigli in questo contesto di domanda
kiedysktos

3
@kiedysktos Questo è un buon punto per cui la chiamata .startWithpotrebbe anche causare un puntatore nullo. Il punto che stavo cercando di chiarire è che Java fornisce un metodo specifico per filtrare oggetti null dai tuoi stream.
xbakesx,

@Mark Booth sì, ovviamente Objects.nonNullè equivalente a != null, la tua opzione è più breve
kiedysktos

1
Non stai creando un elenco di nomi di auto ( String) anziché automobili ( Car)?
user1803551

59

Devi solo filtrare le auto che hanno un nullnome:

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

3
È un vero peccato che questa risposta non sia più votata in quanto sembra essere l'unica risposta che effettivamente risponde alla domanda.
Mark Booth,

@MarkBooth La domanda "Come filtrare i valori null?" sembra essere risposto bene da xbakesx.
vegemite4me,

@MarkBooth Guardando le date in cui hai ragione. Errore mio.
vegemite4me,

Per quanto riguarda le prestazioni, è consigliabile filtrare lo stream due volte O meglio utilizzare il predicato per filtrare? Voglio solo sapere.
Vaibhav_Sharma il

51

Le risposte proposte sono fantastiche. Vorrei solo suggerire un miglioramento per gestire il caso dell'elenco null utilizzando Optional.ofNullable, nuove funzionalità in Java 8 :

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

Quindi, la risposta completa sarà:

 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

5
Cattivo utilizzo di Opzionale. null non dovrebbe mai essere usato come sinonimo di una raccolta vuota in primo luogo.
VGR,

5
@VGR Certo, ma non è quello che succede in pratica. A volte (la maggior parte delle volte) è necessario lavorare con codice su cui hanno lavorato molte persone. A volte ricevi i tuoi dati da interfacce esterne. Per tutti questi casi, Opzionale è di grande utilità.
Johnny,

2
nota che l'auto nulla non è il problema. In questo caso, è la proprietà name che causa problemi. Quindi Objects::nonNullnon risolve il problema poiché l'auto non nulla può avere name == null
kiedysktos

1
Ovviamente @kiedysktos, ma non è quello che volevo mostrare nella risposta. Ma sto accettando quello che dici e modificando la risposta :)
Johnny,

24

Puoi farlo in un unico passaggio del filtro:

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

Se non vuoi chiamare getName()più volte (ad esempio, è una chiamata costosa), puoi farlo:

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

O in modo più sofisticato:

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

L'espansione inline nel secondo esempio è stata preziosa per il mio caso d'uso
Paul

3

Sfruttando il potere di 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())
;

1

puoi usare questo

List<Car> requiredCars = cars.stream()
    .filter (t->  t!= null && StringUtils.startsWith(t.getName(),"M"))
    .collect(Collectors.toList());
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.