Как мы должны управлять потоком 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))
  • Разработчик должен убедиться, что приложение никогда не генерирует null otter или специальный объект NullOtter, с которым нужно иметь дело.

Какой вариант лучше, или любой другой вариант?

Благодарность!

Clement
источник
3
Я бы сказал, что программист должен делать здесь правильные вещи; JVM и компилятор могут только так много. Обратите внимание, однако, что некоторые реализации коллекций не допускают нулевые значения.
fge
18
Вы можете использовать filter(Objects::nonNull)с Objectsотjava.utils
Benj

Ответы:

45

Текущее мышление, похоже, состоит в том, чтобы «терпеть» значения NULL, то есть разрешать их в целом, хотя некоторые операции менее терпимы и могут закончиться вызовом NPE. См. Обсуждение нулей в списке рассылки группы экспертов по лямбда-библиотекам, в частности это сообщение . Впоследствии был достигнут консенсус по варианту № 3 (с заметным возражением со стороны Дуга Ли). Так что да, обеспокоенность ОП по поводу взрыва трубопроводов с помощью NPE обоснована.

Недаром Тони Хоар называл нули «ошибкой на миллиард долларов». Работа с нулевыми значениями - настоящая боль. Даже с классическими коллекциями (без учета лямбда-выражений или потоков) пустые значения проблематичны. Как упоминалось в комментарии fge , некоторые коллекции допускают нули, а другие - нет. С коллекциями, допускающими значения NULL, это вносит двусмысленность в API. Например, с Map.get () возврат null указывает либо на то, что ключ присутствует и его значение равно null, либо на то, что ключ отсутствует. Чтобы устранить неоднозначность этих случаев, нужно проделать дополнительную работу.

Обычно значение null используется для обозначения отсутствия значения. Подход для решения этой проблемы, предложенный для Java SE 8, состоит в том, чтобы ввести новый java.util.Optionalтип, который инкапсулирует наличие / отсутствие значения, а также поведение, заключающееся в предоставлении значения по умолчанию, или выдаче исключения, или вызове функции и т. Д., Если значение отсутствует. Optionalиспользуется только новыми API, однако все остальное в системе все еще вынуждено мириться с возможностью обнуления.

Мой совет - по возможности избегать фактических нулевых ссылок. Из приведенного примера трудно понять, как может существовать «нулевая» выдра. Но если это было необходимо, предложения OP по фильтрации нулевых значений или сопоставлению их с контрольным объектом ( шаблон нулевого объекта ) являются прекрасным подходом.

Стюарт Маркс
источник
3
Зачем избегать нулей? Они широко используются в базах данных.
Раффи Хачадурян
5
@RaffiKhatchadourian: Да, нули используются в базах данных, но это не менее проблематично. Посмотрите это и это и прочтите все ответы и комментарии. Также рассмотрите влияние SQL null на логические выражения: en.wikipedia.org/wiki/… ... это богатый источник ошибок запроса.
Стюарт Маркс
1
@RaffiKhatchadourian Потому что nullотстой, вот почему. Соотв. в обзоре, который я видел (не могу найти), NPE - исключение номер 1 в Java. SQL не является языком программирования высокого уровня, уж точно не функциональным, который презирает null, поэтому ему все равно.
Абхиджит Саркар
91

Хотя ответы на 100% верны, небольшое предложение по улучшению nullобработки регистра самого списка с помощью Optional :

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

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

Джонни
источник
2
Мне это нравится, я избегаю явной проверки на null.
Крис
1
это выглядит лучше всего ясно .. красиво и именно то, что мне нужно
Абдулла Аль Номан
Он явно проверяет значение null, только другим способом. Если разрешено иметь значение null, оно должно быть необязательным, это идея, верно? Запретить все нулевые значения с помощью дополнительных опций. Более того, это плохая практика - возвращать null вместо пустого списка, поэтому, если я увижу это, это показывает два запаха кода: не используются дополнительные опции и не возвращаются пустые потоки. И последний доступен уже более 20 лет, так что это зрелый вопрос ...
Коос Гаделлаа,
что, если я хочу вернуть нуль вместо пустого списка?
Эшберн РК,
@AshburnRK, это плохая практика. Вы должны вернуть пустой список.
Джонни
69

Ответ Стюарта дает прекрасное объяснение, но я хотел бы привести еще один пример.

Я столкнулся с этой проблемой при попытке выполнить в reduceStream, содержащем нулевые значения (на самом деле это было так LongStream.average(), что является типом уменьшения). Поскольку функция average () возвращается OptionalDouble, я предположил, что Stream может содержать значения NULL, но вместо этого было создано исключение NullPointerException. Это связано с объяснением Стюарта null v. Empty.

Итак, как предлагает OP, я добавил такой фильтр:

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

Или, как указано ниже, используйте предикат, предоставляемый Java API:

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

Из обсуждения списка рассылки Стюарт связал: Брайан Гетц о нулях в Streams

bparry
источник
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

Пример того, как избежать null, например, использовать фильтр перед группированием по

Отфильтруйте нулевые экземпляры перед groupingBy.

Вот пример

MyObjectlist.stream()
            .filter(p -> p.getSomeInstance() != null)
            .collect(Collectors.groupingBy(MyObject::getSomeInstance));
Илан М
источник