Filtern Sie Werte nur, wenn nicht null, mit Lambda in Java8

160

Ich habe eine Liste von Objekten sagen car. Ich möchte diese Liste basierend auf einigen Parametern mit Java 8 filtern. Wenn der Parameter jedoch lautet null, wird er ausgelöst NullPointerException. Wie filtere ich Nullwerte heraus?

Der aktuelle Code lautet wie folgt

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

Dies wirft, NullPointerExceptionwenn getName()zurückgegeben wird null.

vaibhavvc1092
quelle
Möchten Sie "Werte nur filtern, wenn sie nicht null sind" oder "Nullwerte herausfiltern"? Das klingt für mich widersprüchlich.
Holger
3
Könnte ich vorschlagen, dass Sie Tunakis Antwort akzeptieren , da es die einzige zu sein scheint, die Ihre Frage tatsächlich beantwortet.
Mark Booth

Antworten:

322

In diesem speziellen Beispiel denke ich, dass @Tagir 100% korrekt ist. Holen Sie es in einen Filter und führen Sie die beiden Überprüfungen durch. Ich würde Optional.ofNullabledas optionale Zeug nicht verwenden, damit Rückgabetypen keine Logik ausführen ... aber wirklich weder hier noch dort.

Ich wollte darauf hinweisen, dass java.util.Objectsdies in einem breiten Fall eine gute Methode dafür ist, also können Sie dies tun:

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

Dadurch werden Ihre Nullobjekte gelöscht. Für alle, die nicht vertraut sind, ist dies die Abkürzung für Folgendes:

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

Um die vorliegende Frage teilweise zu beantworten und die Liste der Fahrzeugnamen zurückzugeben, die mit "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());

Sobald Sie sich an die Kurzschrift Lambdas gewöhnt haben, können Sie auch Folgendes tun:

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

Sobald Sie dies tun, werden Sie leider .map(Car::getName)nur die Liste der Namen zurückgeben, nicht die Autos. Also weniger schön, aber vollständig beantwortet die Frage:

cars.stream()
    .filter(car -> Objects.nonNull(car))
    .filter(car -> Objects.nonNull(car.getName()))
    .filter(car -> car.getName().startsWith("M"))
    .collect(Collectors.toList());
xbakesx
quelle
1
Beachten Sie, dass das Nullauto nicht das Problem ist. In diesem Fall verursacht die Namenseigenschaft Probleme. So Objects::nonNullkann hier nicht verwendet werden, und in letzten Rat sollte es sein , cars.stream() .filter(car -> Objects.nonNull(car.getName()))glaube ich
kiedysktos
1
Übrigens, ich denke, cars.stream() .filter(car -> Objects.nonNull(car.getName()) && car.getName().startsWith("M"))wäre die Zusammenfassung Ihres Ratschlags in diesem Fragenkontext
kiedysktos
3
@kiedysktos Das ist ein guter Punkt, dass das Aufrufen .startWithauch einen Nullzeiger verursachen kann. Der Punkt, den ich ansprechen wollte, ist, dass Java eine Methode speziell zum Herausfiltern von Nullobjekten aus Ihren Streams bereitstellt.
Xbakesx
@ Mark Booth ja, offensichtlich Objects.nonNullist gleichbedeutend mit != null, Ihre Option ist kürzer
kiedysktos
1
Erstellen Sie nicht eine Liste mit Autonamen ( String) statt Autos ( Car)?
user1803551
59

Sie müssen nur die Autos filtern, die einen nullNamen haben:

requiredCars = cars.stream()
                   .filter(c -> c.getName() != null)
                   .filter(c -> c.getName().startsWith("M"));
Tunaki
quelle
3
Es ist wirklich schade, dass diese Antwort nicht höher bewertet wird, da sie die einzige Antwort zu sein scheint, die die Frage tatsächlich beantwortet.
Mark Booth
@MarkBooth Die Frage "Wie filtere ich Nullwerte heraus?" scheint von xbakesx gut beantwortet zu werden.
vegemite4me
@ MarkBooth Wenn Sie sich die Daten ansehen, sind Sie korrekt. Mein Fehler.
vegemite4me
In Bezug auf die Leistung ist es gut, den Stream zweimal zu filtern. Oder verwenden Sie das Prädikat besser zum Filtern? Ich würde es nur gerne wissen.
Vaibhav_Sharma
51

Die vorgeschlagenen Antworten sind großartig. Möchten einfach mal für eine Verbesserung vorzuschlagen , den Fall von null Liste mit zu handhaben Optional.ofNullable, neue Funktion in Java 8 :

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

Die vollständige Antwort lautet also:

 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
quelle
5
Schlechte Verwendung von Optional. null sollte niemals als Synonym für eine leere Sammlung verwendet werden.
VGR
4
@VGR Natürlich, aber das passiert in der Praxis nicht. Manchmal (meistens) müssen Sie mit Code arbeiten, an dem viele Leute gearbeitet haben. Manchmal erhalten Sie Ihre Daten von externen Schnittstellen. In all diesen Fällen ist Optional eine gute Verwendung.
Johnny
1
Beachten Sie, dass das Nullauto nicht das Problem ist. In diesem Fall verursacht die Namenseigenschaft Probleme. So Objects::nonNulllöst nicht das Problem , da nicht-Null - Auto Namen haben == null
kiedysktos
Natürlich @kiedysktos, aber das wollte ich in der Antwort nicht zeigen. Aber ich akzeptiere, was Sie sagen und bearbeite die Antwort :)
Johnny
24

Sie können dies in einem einzelnen Filterschritt tun:

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

Wenn Sie nicht getName()mehrmals anrufen möchten (z. B. ein teurer Anruf), können Sie Folgendes tun:

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

Oder auf raffiniertere Weise:

requiredCars = cars.stream().filter(c -> 
    Optional.ofNullable(c.getName()).filter(name -> name.startsWith("M")).isPresent());
Tagir Valeev
quelle
Die Inline-Erweiterung im zweiten Beispiel war für meinen Anwendungsfall wertvoll
Paul
3

Nutzen Sie die Kraft von 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
quelle
1

Sie können dies verwenden

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