Filtruj wartości tylko wtedy, gdy nie mają wartości null, używając lambda w Java8

160

Mam do powiedzenia listę obiektów car. Chcę przefiltrować tę listę na podstawie jakiegoś parametru przy użyciu języka Java 8. Ale jeśli parametr jest null, wyrzuca NullPointerException. Jak odfiltrować wartości null?

Obecny kod jest następujący

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

To rzuca się, NullPointerExceptionjeśli getName()wróci null.

vaibhavvc1092
źródło
Czy chcesz „filtrować wartości tylko wtedy, gdy nie są puste” czy „filtrować wartości zerowe”? Brzmi mi to w sprzeczności.
Holger
3
Czy mógłbym zasugerować, abyś zaakceptował odpowiedź Tunakiego, ponieważ wydaje się, że jest ona jedyną, która faktycznie odpowiada na twoje pytanie.
Mark Booth

Odpowiedzi:

322

W tym konkretnym przykładzie myślę, że @Tagir jest w 100% poprawny, włóż go do jednego filtra i wykonaj dwie kontrole. Nie użyłbym Optional.ofNullableopcjonalnych rzeczy, które są naprawdę dla typów zwracanych, aby nie wykonywać logiki ... ale tak naprawdę ani tu, ani tam.

Chciałem zaznaczyć, że java.util.Objectsma na to fajną metodę w szerokim przypadku, więc możesz to zrobić:

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

Który usunie twoje puste obiekty. Dla każdego, kto nie jest zaznajomiony, jest to krótka ręka na:

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

Aby częściowo odpowiedzieć na zadane pytanie, należy zwrócić listę nazw samochodów zaczynającą się od "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());

Gdy już przyzwyczaisz się do skróconych lambd, możesz również zrobić to:

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

Niestety, raz .map(Car::getName)zwrócisz tylko listę nazwisk, a nie samochody. Tak mniej piękna, ale w pełni odpowiada na pytanie:

cars.stream()
    .filter(car -> Objects.nonNull(car))
    .filter(car -> Objects.nonNull(car.getName()))
    .filter(car -> car.getName().startsWith("M"))
    .collect(Collectors.toList());
xbakesx
źródło
1
Zauważ, że pusty samochód nie jest problemem. W tym przypadku to właściwość name powoduje problemy. Więc Objects::nonNullnie można tego tutaj wykorzystać, a ostatnią radą powinno być cars.stream() .filter(car -> Objects.nonNull(car.getName()))wierzę
kiedysktos
1
cars.stream() .filter(car -> Objects.nonNull(car.getName()) && car.getName().startsWith("M"))
Swoją drogą
3
@kiedysktos To dobra uwaga, że ​​wywołanie .startWithmoże również spowodować zerowy wskaźnik. Chodziło mi o to, że Java dostarcza metodę specjalnie do filtrowania pustych obiektów ze strumieni.
xbakesx
@Mark Booth tak, oczywiście Objects.nonNulljest to odpowiednik != null, twoja opcja jest krótsza
kiedysktos
1
Czy nie tworzysz listy nazw samochodów ( String) zamiast samochodów ( Car)?
user1803551
59

Wystarczy odfiltrować samochody, które mają nullnazwę:

requiredCars = cars.stream()
                   .filter(c -> c.getName() != null)
                   .filter(c -> c.getName().startsWith("M"));
Tunaki
źródło
3
Szkoda, że ​​ta odpowiedź nie jest bardziej głosowana, ponieważ wydaje się być jedyną odpowiedzią, która faktycznie odpowiada na pytanie.
Mark Booth
@MarkBooth Pytanie „Jak odfiltrować wartości null?” wydaje się, że xbakesx odpowiada dobrze.
vegemite4me
@MarkBooth Patrząc na daty, masz rację. Mój błąd.
vegemite4me
Jeśli chodzi o wydajność, czy dobrze jest filtrować strumień dwukrotnie Czy lepiej użyć predykatu do filtrowania? Po prostu chcę wiedzieć.
Vaibhav_Sharma
51

Proponowane odpowiedzi są świetne. Wystarczy chciałby zaproponować lepszy obsłużyć przypadek liście zerowym użyciu Optional.ofNullable, nowej funkcji w Java 8 :

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

Tak więc pełna odpowiedź będzie brzmiała:

 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
Jasio
źródło
5
Złe użycie Optional. Przede wszystkim null nie powinno być używane jako synonim pustej kolekcji.
VGR
4
@VGR Oczywiście, ale tak nie jest w praktyce. Czasami (przez większość czasu) musisz pracować z kodem, nad którym pracowało wiele osób. Czasami otrzymujesz dane z zewnętrznych interfejsów. We wszystkich tych przypadkach opcja Optional jest świetnym zastosowaniem.
Johnny
1
Zauważ, że pusty samochód nie jest problemem. W tym przypadku to właściwość name powoduje problemy. Więc Objects::nonNullnie rozwiązuje problemu, ponieważ niezerowy samochód może mieć nazwę == null
kiedysktos
Oczywiście @kiedysktos, ale nie to chciałem pokazać w odpowiedzi. Ale akceptuję to, co mówisz i edytuję odpowiedź :)
Johnny
24

Możesz to zrobić w jednym kroku filtrowania:

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

Jeśli nie chcesz dzwonić getName()kilka razy (na przykład jest to drogie połączenie), możesz to zrobić:

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

Lub w bardziej wyrafinowany sposób:

requiredCars = cars.stream().filter(c -> 
    Optional.ofNullable(c.getName()).filter(name -> name.startsWith("M")).isPresent());
Tagir Valeev
źródło
Rozwinięcie inline w drugim przykładzie było cenne w moim przypadku użycia
Paul
3

Wykorzystanie mocy 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
źródło
1

możesz tego użyć

List<Car> requiredCars = cars.stream()
    .filter (t->  t!= null && StringUtils.startsWith(t.getName(),"M"))
    .collect(Collectors.toList());
rzeka
źródło