Як нам управляти потоком jdk8 для нульових значень


88

Привіт колеги розробники Java,

Я знаю, що тема може бути трохи, in advanceоскільки JDK8 ще не випущений (і поки що не ..), але я читав деякі статті про виразів Лямбда, і особливо частину, пов'язану з новим API колекції, відомим як Stream.

Ось приклад, наведений у статті Java Magazine (це алгоритм популяції видри ..):

Set<Otter> otters = getOtters();
System.out.println(otters.stream()
    .filter(o -> !o.isWild())
    .map(o -> o.getKeeper())
    .filter(k -> k.isFemale())
    .into(new ArrayList<>())
    .size());

Моє питання полягає в тому, що трапиться, якщо в середині внутрішньої ітерації Set одна з видр буде нульовою?

Я би очікував, що буде викинуто NullPointerException, але, можливо, я все ще застряг у попередній парадигмі розвитку (нефункціональній), може хтось просвітлить мене, як з цим слід поводитися?

Якщо це справді викине NullPointerException, я вважаю цю функцію досить небезпечною, і її доведеться використовувати лише як показано нижче:

  • Розробник, щоб переконатися, що немає нульового значення (можливо, використовуючи попередній .filter (o -> o! = Null))
  • Розробник повинен переконатися, що додаток ніколи не генерує нульову видру або спеціальний об’єкт NullOtter, з яким потрібно мати справу.

Який найкращий варіант чи будь-який інший варіант?

Дякую!


3
Я б сказав, що програміст повинен робити тут правильну справу; JVM та компілятор можуть зробити лише стільки. Зауважте, що деякі реалізації колекції не дозволяють мати нульові значення.
fge

18
Ви можете використовувати filter(Objects::nonNull)з Objectsfromjava.utils
Benj

Відповіді:


45

Сучасне мислення, здається, полягає в тому, щоб "терпіти" нульові значення, тобто дозволяти їх загалом, хоча деякі операції менш толерантні і в підсумку можуть викинути NPE. Див. Обговорення нульових показників у списку розсилки експертної групи Lambda Libraries, зокрема це повідомлення . Згодом з’явився консенсус щодо варіанту №3 (із помітним запереченням від Дуга Лі). Так що так, занепокоєння ОП з приводу вибуху трубопроводів з НПЕ є дійсним.

Не дарма Тоні Хоаре називав нулі "помилкою на мільярд доларів". Робота з нулями - справжній біль. Навіть у класичних колекціях (без урахування лямбда або потоків) нульові проблеми є проблематичними. Як згадано у коментарі fge , деякі колекції дозволяють нульові значення, а інші - ні. З колекціями, які дозволяють нульові значення, це вносить неоднозначності в API. Наприклад, у Map.get () нульове повернення вказує або на те, що ключ присутній, і його значення є нульовим, або на відсутність ключа. Потрібно виконати додаткову роботу, щоб розрізнити ці справи.

Звичайне використання для null - це позначення відсутності значення. Підхід до вирішення цього, запропонованого для Java SE 8, полягає у впровадженні нового java.util.Optionalтипу, який інкапсулює наявність / відсутність значення, поряд із поведінкою подання значення за замовчуванням, або створення винятку, або виклику функції тощо, якщо значення відсутнє. Optionalвикористовується лише новими API, однак усе інше в системі все ще має терпіти можливість нулів.

Моя порада - якомога більше уникати фактичних нульових посилань. На наведеному прикладі важко зрозуміти, як може бути "нульова" видра. Але якщо це було необхідно, пропозиції OP щодо фільтрації нульових значень або відображення їх до сторожового об’єкта ( Null Object Pattern ) є чудовими підходами.


3
Чому уникати нулів? Вони широко використовуються в базах даних.
Раффі Хатчадуріан,

5
@RaffiKhatchadourian Так, нульові значення використовуються в базах даних, але однаково проблематичні. Подивіться це і це і прочитайте всі відповіді та коментарі. Також розглянемо вплив SQL null на логічні вирази: en.wikipedia.org/wiki/… ... це багате джерело помилок запиту.
Стюарт Маркс

1
@RaffiKhatchadourian Тому що nullвідстій, ось чому. Відповідно до для опитування, яке я бачив (не можу знайти), NPE є винятком номер 1 у Java. SQL не є мовою програмування високого рівня, звичайно, не функціональною, яка зневажає null, тому їй все одно.
Абхіджіт Саркар

91

Незважаючи на те, що відповіді на 100% правильні, невелика пропозиція покращити nullобробку справ самого списку за допомогою необов’язкового :

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

Ця частина Optional.ofNullable(listOfStuff).orElseGet(Collections::emptyList)дозволить вам добре обробляти випадок, коли listOfStuffє null, і повертати emptyList замість того, щоб провалити NullPointerException.


2
Мені подобається, уникає явної перевірки на нуль.
Кріс

1
це виглядає найкраще зрозуміло .. приємно і саме те, що мені було потрібно
Абдулла Аль Номан

Це явна перевірка на нуль, лише по-іншому. Якщо дозволено нуль, це має бути необов’язково, це ідея, так? Заборонити всі нульові значення необов’язковими. Більше того, поганою практикою є повернення null замість порожнього списку, тому він показує два запахи коду, якщо я бачу це: не використовується Optionals і не повертаються порожні потоки. А остання доступна понад 20 років, отже, це вже зріло ...
Koos Gadellaa

що робити, якщо я хочу повернути нульовий замість порожнього списку?
Ashburn RK

@AshburnRK це погана практика. Ви повинні повернути порожній список.
Джонні

69

Відповідь Стюарта дає чудове пояснення, але я хотів би навести інший приклад.

Я зіткнувся з цією проблемою під час спроби виконати reduceпотік, що містить нульові значення (насправді це було LongStream.average(), що є одним із видів зменшення). Оскільки OptionalDoublereturn () повертається , я припустив, що потік може містити нулі, але замість цього було викинуто NullPointerException. Це пояснюється поясненням Стюарта null v. Empty.

Отже, як пропонується OP, я додав такий фільтр:

list.stream()
    .filter(o -> o != null)
    .reduce(..);

Або, як вказано нижче, використовуйте предикат, наданий Java API:

list.stream()
    .filter(Objects::nonNull)
    .reduce(..);

З обговорення списку розсилки Стюарт зв’язав: Брайан Гетц про нульові значення в Streams


19

Якщо ви просто хочете відфільтрувати нульові значення з потоку, ви можете просто використати посилання на метод java.util.Objects.nonNull (Object) . З його документації:

Цей метод існує для використання як предикат ,filter(Objects::nonNull)

Наприклад:

List<String> list = Arrays.asList( null, "Foo", null, "Bar", null, null);

list.stream()
    .filter( Objects::nonNull )  // <-- Filter out null values
    .forEach( System.out::println );

Буде надруковано:

Foo
Bar

7

Приклад того, як уникнути нуля, наприклад, використовувати фільтр перед groupingBy

Відфільтруйте нульові екземпляри перед групуваннямBy.

Ось приклад

MyObjectlist.stream()
            .filter(p -> p.getSomeInstance() != null)
            .collect(Collectors.groupingBy(MyObject::getSomeInstance));
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.