Должен ли получатель Java 8 возвращать необязательный тип?

288

Optional Тип, введенный в Java 8, является новой вещью для многих разработчиков.

Является ли метод получения, возвращающий Optional<Foo>тип вместо классического, Fooхорошей практикой? Предположим, что значение может быть null.

leonprou
источник
8
Несмотря на то, что это может привлечь к себе объективные ответы, это хороший вопрос. Я с нетерпением жду ответа с реальными фактами на эту тему.
Джастин
8
Вопрос в том, неизбежна ли эта недолговечность. Компонент может иметь свойство, которое может иметь значение NULL, но программист, использующий этот компонент, может решить не использовать это свойство null. Таким образом, программист не должен иметь дело с этим Optional. Или, другими словами, nullдействительно представляет отсутствие значения, такого как результат поиска (где Optionalэто уместно), или является nullпросто одним из множества возможных значений.
Хольгер
1
Смотрите также обсуждение @NotNullаннотаций: stackoverflow.com/q/4963300/873282
koppor

Ответы:

517

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

Например, вы, вероятно, никогда не должны использовать это для чего-то, что возвращает массив результатов или список результатов; вместо этого верните пустой массив или список. Вы почти никогда не должны использовать его в качестве поля чего-либо или параметра метода.

Я думаю, что регулярное использование его в качестве возвращаемого значения для получателей определенно было бы чрезмерным.

В Optional нет ничего плохого в том, что его следует избегать, просто это не то, чего хотят многие люди, и, соответственно, мы были весьма обеспокоены риском ревностного чрезмерного использования.

(Объявление государственной службы: НИКОГДА не вызывайте, Optional.getесли только вы не можете доказать, что оно никогда не будет нулевым; вместо этого используйте один из безопасных методов, таких как orElseили ifPresent. В ретроспективе мы должны были вызвать getчто-то вроде getOrElseThrowNoSuchElementExceptionили что-то, что сделало бы гораздо яснее, что это очень опасный метод это подорвало всю цель, Optionalв первую очередь. Извлеченный урок. (ОБНОВЛЕНИЕ: Java 10 имеет Optional.orElseThrow(), что семантически эквивалентно get(), но чье имя больше подходит.))

Брайан Гетц
источник
24
(Что касается последней части) ... и когда мы уверены, что ценность никогда не будет, nullмы можем использовать orElseThrow(AssertionError::new), хм или orElseThrow(NullPointerException::new)...
Хольгер
26
Что бы вы сделали по- другому , если ваше намерение было в том , чтобы ввести общее назначение Может или иной тип? Есть ли способ, которым Optional не соответствует всем требованиям, или это просто, что введение Optional во всем новом API сделало бы его не-Java?
Дэвид Моулз
22
Под «универсальным типом» я подразумевал встраивание его в систему типов языка, а не предоставление библиотечного класса, который приближается к нему. (У некоторых языков есть типы для T? (T или нуль) и T! (Не обнуляемый T)) Необязательный просто класс; мы не можем сделать неявные преобразования между Foo и Optional <Foo>, как мы могли бы сделать с поддержкой языка.
Брайан Гетц
11
Какое-то время я удивлялся, почему в мире Java упор делается на факультативный, а не на лучший статический анализ. У Optional есть некоторые преимущества, но огромное преимущество nullзаключается в обратной совместимости; Map::getвозвращает nullable V, а не an Optional<V>, и это никогда не изменится. Это можно было бы легко аннотировать @Nullable, хотя. Теперь у нас есть два способа выразить недостаток стоимости, плюс меньше стимулов для проведения статического анализа, который выглядит как худшая позиция.
yshavit
21
Я понятия не имел, что использовать его в качестве возвращаемого значения для свойства - это не то, что вы хотели, пока я не прочитал этот ответ здесь, в StackOverflow. На самом деле, меня заставили поверить, что именно это и было задумано после прочтения этой статьи на сайте Oracle: oracle.com/technetwork/articles/java/… Спасибо за разъяснения
Джейсон Томпсон,
73

Проведя небольшое собственное исследование, я наткнулся на несколько вещей, которые могли бы предложить, когда это уместно. Наиболее авторитетной является следующая цитата из статьи Oracle:

«Важно отметить, что цель класса Optional состоит не в том, чтобы заменить каждую нулевую ссылку . Вместо этого его цель - помочь разработать более понятные API, чтобы, просто читая сигнатуру метода, вы могли сказать, может ожидать необязательное значение. Это заставляет вас активно разворачивать необязательное значение, чтобы справиться с отсутствием значения. " - Устали от исключений нулевого указателя? Подумайте об использовании Java SE 8 необязательно!

Я также нашел этот отрывок из Java 8 Необязательно: как его использовать

«Необязательный не предназначен для использования в этих контекстах, так как он ничего нам не купит:

  • в слое модели предметной области (не сериализуемо)
  • в DTO (по той же причине)
  • во входных параметрах методов
  • в параметрах конструктора "

Который также, кажется, поднимает некоторые действительные пункты.

Я не смог найти никаких негативных коннотаций или красных флажков, чтобы предположить, что этого Optionalследует избегать. Я думаю, что общая идея, если это полезно или улучшает удобство использования вашего API, используйте его.

Джастин
источник
11
stackoverflow.com/questions/25693309 - кажется, что Джексон уже поддерживает его, поэтому «не сериализуемость» больше не
считается
1
Я предлагаю иметь ваш ответный адрес, почему использование Optionalво входных параметрах методов (в частности, конструкторов) «не даст нам ничего». dolszewski.com/java/java-8-optional-use-cases содержит хорошее объяснение.
Гили
Я обнаружил, что для получения дополнительных результатов, которые необходимо преобразовать в другие API, требуется дополнительный параметр. В результате получается довольно понятный API. См. Stackoverflow.com/a/31923105/105870
Карл Язычник
1
Ссылка не работает. Не могли бы вы обновить ответ?
softarn
2
Проблема в том, что он часто не улучшает API, несмотря на благие намерения разработчика. Я собрал примеры плохого использования Optional , все взято из производственного кода, который вы можете проверить.
MiguelMunoz
20

Я бы сказал, что в целом было бы неплохо использовать необязательный тип для возвращаемых значений, которые могут иметь значение NULL. Однако в отношении фреймворков я предполагаю, что замена классических геттеров необязательными типами вызовет много проблем при работе с фреймворками (например, Hibernate), которые полагаются на соглашения о кодировании для геттеров и сеттеров.

Claas Wilke
источник
14
Этот совет - именно то, что я имел в виду под «мы обеспокоены риском ревностного злоупотребления» в stackoverflow.com/a/26328555/3553087 .
Брайан Гетц
13

Причина Optionalбыла добавлена ​​в Java, потому что это:

return Arrays.asList(enclosingInfo.getEnclosingClass().getDeclaredMethods())
    .stream()
    .filter(m -> Objects.equals(m.getName(), enclosingInfo.getName())
    .filter(m ->  Arrays.equals(m.getParameterTypes(), parameterClasses))
    .filter(m -> Objects.equals(m.getReturnType(), returnType))
    .findFirst()
    .getOrThrow(() -> new InternalError(...));

чище, чем это:

Method matching =
    Arrays.asList(enclosingInfo.getEnclosingClass().getDeclaredMethods())
    .stream()
    .filter(m -> Objects.equals(m.getName(), enclosingInfo.getName())
    .filter(m ->  Arrays.equals(m.getParameterTypes(), parameterClasses))
    .filter(m -> Objects.equals(m.getReturnType(), returnType))
    .getFirst();
if (matching == null)
  throw new InternalError("Enclosing method not found");
return matching;

Я хочу сказать, что Optional был написан для поддержки функционального программирования , которое было добавлено в Java в то же время. (Этот пример любезно предоставлен блогом Брайана Гетца . Лучшим примером может быть использование orElse()метода, так как этот код в любом случае вызовет исключение, но вы получите представление.)

Но сейчас люди используют Optional по совсем другой причине. Они используют это для устранения недостатка в дизайне языка. Недостаток заключается в следующем: невозможно указать, какие из параметров API и возвращаемые значения могут быть нулевыми. Это может быть упомянуто в javadocs, но большинство разработчиков даже не пишут javadocs для своего кода, и не многие проверяют javadocs при их написании. Таким образом, это приводит к большому количеству кода, который всегда проверяет наличие нулевых значений перед их использованием, даже если они часто не могут быть нулевыми, поскольку они уже неоднократно проверялись девять или десять раз в стеке вызовов.

Я думаю, что была настоящая жажда устранить этот недостаток, потому что так много людей, которые видели новый класс Optional, предполагали, что его цель - внести ясность в API. Вот почему люди задают такие вопросы, как «должен ли получатель вернуть опционально?» Нет, они, вероятно, не должны, если вы не ожидаете, что геттер будет использоваться в функциональном программировании, что очень маловероятно. Фактически, если вы посмотрите, где Optional используется в Java API, то это в основном в классах Stream, которые являются ядром функционального программирования. (Я не очень тщательно проверил, но классы Stream могут быть единственным местом, где они используются.)

Если вы планируете использовать метод получения в небольшом количестве функционального кода, было бы неплохо иметь стандартный метод получения и второй метод, возвращающий Optional.

Да, и если вам нужно, чтобы ваш класс был сериализуемым, вы не должны использовать Optional.

Необязательные решения являются очень плохим решением для недостатка API, потому что они очень многословны и б) они никогда не предназначались для решения этой проблемы.

Гораздо лучшим решением для недостатка API является Nullness Checker . Это процессор аннотаций, который позволяет вам указать, какие параметры и возвращаемые значения могут иметь значение null, пометив их @Nullable. Таким образом, компилятор может сканировать код и выяснить, передается ли значение, которое может фактически быть нулевым, значению, для которого нулевое значение не допускается. По умолчанию предполагается, что ничто не может быть пустым, если это не аннотировано. Таким образом, вам не нужно беспокоиться о нулевых значениях. Передача нулевого значения параметру приведет к ошибке компилятора. Тестирование объекта на нулевое значение, которое не может быть нулевым, выдает предупреждение компилятора. Результатом этого является изменение NullPointerException от ошибки времени выполнения до ошибки времени компиляции.

Это меняет все.

Что касается ваших добытчиков, не используйте Optional. И попробуйте создать свои классы, чтобы ни один из участников не мог быть нулевым. И, возможно, попробуйте добавить Nullness Checker в ваш проект и объявить ваши методы получения и установки @Nullable, если они в этом нуждаются. Я сделал это только с новыми проектами. Он, вероятно, выдает много предупреждений в существующих проектах, написанных с множеством лишних тестов на ноль, поэтому может быть сложно модернизировать. Но это также поймает много ошибок. Я люблю это. Мой код намного чище и надежнее из-за этого.

(Существует также новый язык, который решает эту проблему. Kotlin, который компилируется в байт-код Java, позволяет вам указать, может ли объект быть нулевым, когда вы объявляете его. Это более чистый подход.)

Приложение к Исходному сообщению (версия 2)

После долгих размышлений я неохотно пришел к выводу, что допустимо возвращать Optional при одном условии: что полученное значение может фактически быть нулевым. Я видел много кода, где люди обычно возвращают Optional от получателей, которые не могут вернуть null. Я считаю это очень плохой практикой кодирования, которая только добавляет сложности к коду, что повышает вероятность ошибок. Но когда возвращаемое значение может фактически быть нулевым, продолжайте и оберните его внутри Необязательного.

Помните, что методы, предназначенные для функционального программирования и требующие ссылки на функцию, будут (и должны) записываться в двух формах, одна из которых использует Optional. Например, Optional.map()и Optional.flatMap()те , и другие берут ссылки на функции. Первый принимает ссылку на обычный получатель, а второй принимает тот, который возвращает Необязательный. Таким образом, вы никому не делаете одолжение, возвращая Optional, где значение не может быть нулевым.

Сказав все это, я все еще вижу, что подход, применяемый средством проверки на нулевое значение, является лучшим способом справиться с пустыми значениями, поскольку они превращают исключения NullPointerException из ошибок времени выполнения в ошибки времени компиляции.

MiguelMunoz
источник
2
Этот ответ кажется мне наиболее подходящим. Необязательно был добавлен только в Java 8, когда были добавлены потоки. И только потоковые функции возвращают необязательные, насколько я видел.
Archit
1
Я думаю, что это один из лучших ответов. Люди знают, что такое факультативно и как оно работает. Но самая запутанная часть заключается в том, где / как его использовать и как его использовать. Прочитав это, он избавит вас от многих сомнений.
ParagFlume
3

Если вы используете современные сериализаторы и другие фреймворки, которые понимают, Optionalто я обнаружил, что эти рекомендации хорошо работают при написании Entitybean-компонентов и слоев домена:

  1. Если уровень сериализации (обычно БД) допускает nullзначение для ячейки в столбце BARтаблицы FOO, то получатель Foo.getBar()может вернуть Optionalуказание разработчику, что разумно ожидать, что это значение будет нулевым, и они должны это обработать. Если БД гарантирует, что значение не будет нулевым, то получатель не должен переносить это в Optional.
  2. Foo.barдолжно быть privateи не быть Optional. Там действительно нет причин для этого, Optionalесли это так private.
  3. Сеттер Foo.setBar(String bar)должен принять тип, barа не Optional . Если можно использовать nullаргумент, укажите это в комментарии JavaDoc. Если это не нормально использовать nullв IllegalArgumentExceptionили подходящую бизнес - логику это, имхо, более уместно.
  4. Конструкторам не нужны Optionalаргументы (по причинам, аналогичным пункту 3). Обычно я включаю только аргументы в конструктор, которые должны быть ненулевыми в базе данных сериализации.

Для того, чтобы сделать выше более эффективным, вы можете редактировать свои шаблоны IDE для создания методов получения и соответствующие шаблоны toString(), и equals(Obj o)т.д. , или полей используют непосредственно для тех , кто (большинство IDE генераторов уже иметь дело с нулямами).

отметка
источник