Filter values only if not null using lambda in Java8
Asked Answered
P

7

256

I have a list of objects say car. I want to filter this list based on some parameter using Java 8. But if the parameter is null, it throws NullPointerException. How to filter out null values?

Current code is as follows

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

This throws NullPointerException if getName() returns null.

Pyle answered 1/10, 2015 at 9:36 Comment(3)
Do you wanna “filter values only if not null” or “filter out null values”? That sounds contradicting to me.Shortsighted
Could I suggest that you accept Tunaki's answer as it appears to be the only one which actually answers your question.Admissible
What about kotlin? )) requiredCars = cars.filter {c -> c?.name?.startsWith("M"))};Judy
M
520

In this particular example, I think @Tagir is 100% correct get it into one filter and do the two checks. I wouldn't use Optional.ofNullable the Optional stuff is really for return types not to be doing logic... but really neither here nor there.

I wanted to point out that java.util.Objects has a nice method for this in a broad case, so you can do this:

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

Which will clear out your null objects. For anyone not familiar, that's the short-hand for the following:

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

To partially answer the question at hand to return the list of car names that starts with "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());

Once you get used to the shorthand lambdas you could also do this:

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

Unfortunately once you .map(Car::getName) you'll only be returning the list of names, not the cars. So less beautiful but fully answers the question:

    cars.stream()
        .filter(car -> Objects.nonNull(car))
        .filter(car -> Objects.nonNull(car.getName()))
        .filter(car -> car.getName().startsWith("M"))
        .collect(Collectors.toList());
Mohawk answered 23/5, 2016 at 21:39 Comment(7)
note that the null car isn't the issue. In this case, it's name property causing problems. So Objects::nonNull can't be used here, and in last advice it should be cars.stream() .filter(car -> Objects.nonNull(car.getName())) I believeCoccid
BTW, I think cars.stream() .filter(car -> Objects.nonNull(car.getName()) && car.getName().startsWith("M")) would be the summary of your advice in this question contextCoccid
@Coccid That's a good point that calling .startWith could also cause a null pointer. The point I was trying to make is that Java supplies a method specifically for filtering out null objects from your streams.Mohawk
@Mark Booth yes, obviously Objects.nonNull is equivalent to != null, your option is shorterCoccid
Aren't you creating a list of car names (String) instead cars (Car)?Kerouac
Definitely right, once you map to getName it's just a list of Strings. I'll update the answer. Thank you!Mohawk
It's more efficient to use the method reference Objects:nonNull (first option) than using the arrow operator that instance an object.Dempstor
M
91

You just need to filter the cars that have a null name:

requiredCars = cars.stream()
                   .filter(c -> c.getName() != null)
                   .filter(c -> c.getName().startsWith("M"));
Michi answered 1/10, 2015 at 9:37 Comment(4)
It's a real shame that this answer isn't more highly voted as it appears to be the only answer that actually answers the question.Admissible
@MarkBooth The question "How to filter out null values?" seems to be answered well by xbakesx.Mucus
@MarkBooth Looking at the dates you are correct. My mistake.Mucus
Performance wise is it good to filter the stream twice Or better use the predicate for filtering? Just want to know.Maclean
K
68

The proposed answers are great. Just would like to suggest an improvement to handle the case of null list using Optional.ofNullable, new feature in Java 8:

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

So, the full answer will be:

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
Kirkman answered 29/8, 2016 at 14:36 Comment(4)
Bad use of Optional. null should never be used as a synonym for an empty Collection in the first place.Ephesians
@Ephesians Of course, but that is not what happens in practice. Sometimes (most of the time) you need to work with code that a lot of people worked on. Sometime you receive your data from external interfaces. For all those cases, Optional is a great use.Kirkman
note that the null car isn't the issue. In this case, it's name property causing problems. So Objects::nonNull doesn't solve the problem since non-null car can have name==nullCoccid
Of course @kiedysktos, but that's not what I wanted to show in the answer. But, I'm accepting what you saying and editing the answer :)Kirkman
I
32

You can do this in single filter step:

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

If you don't want to call getName() several times (for example, it's expensive call), you can do this:

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

Or in more sophisticated way:

requiredCars = cars.stream().filter(c -> 
    Optional.ofNullable(c.getName()).filter(name -> name.startsWith("M")).isPresent());
Inclement answered 1/10, 2015 at 10:11 Comment(1)
The inline expansion in the second example was valuable for my use caseFranke
R
4

you can use this

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

Reasoning answered 19/7, 2019 at 1:37 Comment(0)
D
4

Leveraging the power of 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())
;
Dear answered 31/1, 2020 at 17:16 Comment(1)
Using the complexity of Optional to replace a simple boolean expression is bad practice imho.Prospero
I
0

You can apply Objects.nonNull or you can put null check on car object inside filter and also apply other condition as well in the same filter method.

List<Car> list = carsList.stream()
         .filter(c-> Objects.nonNull(c) && c.getName().startWith("M"))
         .collect(Collectors.toList());

Incentive answered 15/4 at 5:52 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.