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


160

У мене є перелік об'єктів скажімо car. Я хочу фільтрувати цей список на основі якогось параметра за допомогою Java 8. Але якщо параметр є null, він кидає NullPointerException. Як відфільтрувати нульові значення?

Поточний код такий

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

Це кидає, NullPointerExceptionякщо getName()повернеться null.


Ви хочете "фільтрувати значення лише, якщо не нульові" чи "відфільтрувати нульові значення"? Це звучить для мене суперечливо.
Холгер

3
Чи можу я запропонувати вам прийняти відповідь Тунакі, оскільки, здається, це єдиний, який насправді відповідає на ваше запитання.
Марк Бут

Відповіді:


322

У цьому конкретному прикладі я вважаю, що @Tagir на 100% правильний, введіть його в один фільтр і зробіть дві перевірки. Я б не використовував Optional.ofNullableФакультативні речі справді для того, щоб типи повернення не виконували логіку ... але насправді ні тут, ні там.

Я хотів би зазначити, що 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());

1
зауважте, що нульовий автомобіль - це не проблема. У цьому випадку властивість імені викликає проблеми. Так що 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

@Mark Booth так, очевидно Objects.nonNull, еквівалентно != null, ваш варіант коротший
kiedysktos

1
Ви не створюєте перелік назв автомобілів ( String) замість автомобілів ( Car)?
user1803551

59

Вам просто потрібно відфільтрувати машини, які мають nullназву:

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

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

4
@VGR Звичайно, але це не те, що відбувається на практиці. Іноді (більшу частину часу) потрібно працювати з кодом, над яким працювало багато людей. Колись ви отримуєте свої дані із зовнішніх інтерфейсів. Для всіх тих випадків, необов'язково - це велике використання.
Джонні

1
зауважте, що нульовий автомобіль - це не проблема. У цьому випадку властивість імені викликає проблеми. Тому Objects::nonNullне вирішує проблему, оскільки ненульовий автомобіль може мати ім’я == null
kiedysktos

Звичайно @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())
;

1

Ви можете використовувати це

List<Car> requiredCars = cars.stream()
    .filter (t->  t!= null && StringUtils.startsWith(t.getName(),"M"))
    .collect(Collectors.toList());
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.