Фильтруйте значения, только если не нуль, используя лямбду в Java8

161

У меня есть список объектов, скажем car. Я хочу отфильтровать этот список на основе какого-либо параметра, используя Java 8. Но если параметр есть null, он выбрасывает NullPointerException. Как отфильтровать нулевые значения?

Текущий код выглядит следующим образом

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

Это бросает, NullPointerExceptionесли getName()возвращается null.

vaibhavvc1092
источник
Вы хотите «фильтровать значения только если не ноль» или «отфильтровывать нулевые значения»? Это звучит противоречащим мне.
Хольгер
3
Могу ли я предложить вам принять ответ Тунаки, так как он кажется единственным, который действительно отвечает на ваш вопрос.
Марк Бут

Ответы:

323

В этом конкретном примере я думаю, что @Tagir на 100% корректен, поместите его в один фильтр и выполните две проверки. Я бы не стал использовать Optional.ofNullableOptional, чтобы возвращаемые типы не делали логику ... но на самом деле ни здесь, ни там.

Я хотел бы отметить, что для этого java.util.Objectsесть хороший метод в широком случае, так что вы можете сделать это:

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

Который очистит ваши нулевые объекты. Для тех, кто не знаком, это сокращение для следующего:

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

Чтобы частично ответить на поставленный вопрос, вернем список имен автомобилей, который начинается с "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());

Как только вы привыкнете к сокращенной лямбде, вы также можете сделать это:

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

К сожалению, однажды вы .map(Car::getName)вернете список имен, а не автомобили. Так менее красиво, но полностью отвечает на вопрос:

cars.stream()
    .filter(car -> Objects.nonNull(car))
    .filter(car -> Objects.nonNull(car.getName()))
    .filter(car -> car.getName().startsWith("M"))
    .collect(Collectors.toList());
xbakesx
источник
1
обратите внимание, что нулевая машина не проблема. В этом случае это свойство name вызывает проблемы. Так что Objects::nonNullне может быть использован здесь, и в последнем совете это должно быть, cars.stream() .filter(car -> Objects.nonNull(car.getName()))я верю
kiedysktos
1
Кстати, я думаю, cars.stream() .filter(car -> Objects.nonNull(car.getName()) && car.getName().startsWith("M"))что будет краткое изложение вашего совета в этом вопросе контекста
kiedysktos
3
@kiedysktos Хорошо, что вызов .startWithможет также привести к нулевому указателю. Я пытался подчеркнуть, что Java предоставляет метод, специально предназначенный для фильтрации пустых объектов из ваших потоков.
xbakesx
@ Марк Бут, да, очевидно Objects.nonNull, эквивалентен != null, ваш вариант короче
kiedysktos
1
Разве вы не создаете список имен автомобилей ( String) вместо автомобилей ( Car)?
user1803551
59

Вам просто нужно отфильтровать автомобили, которые имеют nullимя:

requiredCars = cars.stream()
                   .filter(c -> c.getName() != null)
                   .filter(c -> c.getName().startsWith("M"));
Tunaki
источник
3
Стыдно, что за этот ответ не голосуют более высоко, так как он кажется единственным ответом, который действительно отвечает на вопрос.
Марк Бут
@MarkBooth Вопрос "Как отфильтровать нулевые значения?" похоже, хорошо ответил xbakesx.
vegemite4me
@MarkBooth Глядя на даты, которые вы правы. Моя ошибка.
vegemite4me
С точки зрения производительности, хорошо ли фильтровать поток дважды или лучше использовать предикат для фильтрации? Просто хочу знать.
Vaibhav_Sharma
51

Предлагаемые ответы великолепны. Просто хотел бы предложить улучшение для обработки случая использования нулевого списка Optional.ofNullable, новая функция в Java 8 :

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

Итак, полный ответ будет:

 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
Неправильное использование Необязательно. Во-первых, null никогда не должен использоваться как синоним пустой коллекции.
VGR
5
@VGR Конечно, но это не то, что происходит на практике. Иногда (в большинстве случаев) вам нужно работать с кодом, над которым работало много людей. Иногда вы получаете ваши данные от внешних интерфейсов. Для всех этих случаев, Optional - отличное применение.
Джонни
2
обратите внимание, что нулевая машина не проблема. В этом случае это свойство name вызывает проблемы. Так Objects::nonNullчто не решает проблему, поскольку ненулевой автомобиль может иметь имя == null
kiedysktos
1
Конечно @kiedysktos, но это не то, что я хотел показать в ответе. Но я принимаю то, что вы говорите, и редактирую ответ :)
Джонни
24

Вы можете сделать это за один шаг фильтра:

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

Если вы не хотите звонить getName()несколько раз (например, это дорогой звонок), вы можете сделать это:

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

Или более изощренным способом:

requiredCars = cars.stream().filter(c -> 
    Optional.ofNullable(c.getName()).filter(name -> name.startsWith("M")).isPresent());
Тагир Валеев
источник
Встроенное расширение во втором примере было полезным для моего варианта использования
Пол
3

Используя силу 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
источник
1

Вы можете использовать это

List<Car> requiredCars = cars.stream()
    .filter (t->  t!= null && StringUtils.startsWith(t.getName(),"M"))
    .collect(Collectors.toList());
riverfan
источник