Должны ли геттеры Java 8 возвращать необязательный тип?
Optional
тип, представленный в Java 8, является чем-то новым для многих разработчиков.
Является ли метод геттера, возвращающий Optional<Foo>
тип вместо классического Foo
, хорошей практикой? Предположим, что значение может быть null
.
Переведено автоматически
Ответ 1
Конечно, люди будут делать то, что они хотят. Но у нас было четкое намерение при добавлении этой функции, и это был не тип Maybe общего назначения, как бы многим людям ни хотелось, чтобы мы это сделали. Наше намерение состояло в том, чтобы предоставить ограниченный механизм для типов, возвращаемых библиотечными методами, где требовался четкий способ представления "нет результата", и использование null
для такого было в подавляющем большинстве случаев причиной ошибок.
Например, вам, вероятно, никогда не следует использовать его для чего-либо, что возвращает массив результатов или список результатов; вместо этого возвращайте пустой массив или список. Вам почти никогда не следует использовать его как поле чего-либо или параметр метода.
Я думаю, что обычное использование его в качестве возвращаемого значения для геттеров определенно было бы чрезмерным.
В Optional нет ничего плохого в том, что его следует избегать, просто это не то, чего многие люди хотели бы, и, соответственно, мы были справедливо обеспокоены риском чрезмерного использования.
(Объявление общедоступной службы: НИКОГДА не вызывайте Optional.get
, если вы не можете доказать, что это никогда не будет null; вместо этого используйте один из безопасных методов, таких как orElse
или ifPresent
. Оглядываясь назад, мы должны были вызвать get
что-то вроде getOrElseThrowNoSuchElementException
или что-то еще, что сделало бы намного яснее, что это был очень опасный метод, который подрывал всю цель Optional
в первую очередь. Урок усвоен. (ОБНОВЛЕНИЕ: в Java 10 есть Optional.orElseThrow()
, что семантически эквивалентно get()
, но чье имя более подходит.))
Ответ 2
После небольшого собственного исследования я наткнулся на ряд фактов, которые могут подсказать, когда это уместно. Наиболее авторитетной является следующая цитата из статьи Oracle:
"Важно отметить, что целью необязательного класса является не замена каждой отдельной нулевой ссылки. Вместо этого его цель - помочь разработать более понятные API, чтобы, просто прочитав сигнатуру метода, вы могли определить, можно ли ожидать необязательного значения. Это вынуждает вас активно разворачивать необязательный тип, чтобы иметь дело с отсутствием значения. " - Устали от исключений с нулевым указателем? Рассмотрите возможность использования необязательного типа Java SE 8!
Я также нашел этот отрывок из Java 8 Optional: как его использовать
"Необязательный не предназначен для использования в этих контекстах, поскольку он нам ничего не даст:
- на уровне модели предметной области (не сериализуемый)
- в DTO (по той же причине)
- во входных параметрах методов
- в параметрах конструктора "
Что также, кажется, поднимает некоторые допустимые вопросы.
Я не смог найти никаких негативных коннотаций или красных флажков, указывающих на то, что Optional
этого следует избегать. Я думаю, общая идея такова: если это полезно или улучшает удобство использования вашего API, используйте это.
Ответ 3
Причина, по которой 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 по мере написания. Таким образом, это приводит к большому количеству кода, который всегда проверяет наличие нулевых значений перед их использованием, даже если они часто не могут быть нулевыми, потому что они уже были повторно проверены девять или десять раз в стеке вызовов.
Я думаю, что была реальная жажда устранить этот недостаток, потому что очень многие люди, увидевшие новый необязательный класс, предположили, что его целью было внести ясность в API. Вот почему люди задают вопросы типа "должны ли геттеры возвращать необязательные?" Нет, вероятно, они не должны, если только вы не ожидаете, что геттер будет использоваться в функциональном программировании, что очень маловероятно. На самом деле, если вы посмотрите, где Optional используется в Java API, это в основном в классах Stream , которые являются ядром функционального программирования. (Я не проверял очень тщательно, но классы Stream могут быть единственным местом, где они используются.)
Если вы планируете использовать геттер в некотором функциональном коде, было бы неплохо иметь стандартный геттер и второй, который возвращает необязательный.
О, и если вам нужно, чтобы ваш класс был сериализуемым, вам ни в коем случае не следует использовать Optional .
Опции являются очень плохим решением проблемы API, потому что а) они очень подробные, и б) они никогда не предназначались для решения этой проблемы в первую очередь.
Гораздо лучшим решением проблемы с API является проверка на нулевое значение. Это обработчик аннотаций, который позволяет вам указывать, каким параметрам и возвращаемым значениям разрешено принимать значение null, помечая их с помощью @Nullable . Таким образом, компилятор может просканировать код и выяснить, передается ли значение, которое на самом деле может быть null, в значение, где null недопустимо. По умолчанию предполагается, что ничто не может быть нулевым, если оно не помечено таким образом. Таким образом, вам не нужно беспокоиться о значениях null. Передача значения null параметру приведет к ошибке компилятора. Проверка объекта на null, который не может быть null, выдает предупреждение компилятора. Результатом этого является изменение NullPointerException с ошибки времени выполнения на ошибку времени компиляции.
Это все меняет.
Что касается ваших геттеров, не используйте Optional . И постарайтесь спроектировать свои классы так, чтобы ни один из членов не мог быть null. И, возможно, попробуйте добавить средство проверки на нулевость в свой проект и объявить ваши геттеры и установочные параметры @Nullable, если им это нужно. Я делал это только с новыми проектами. Вероятно, в существующих проектах, написанных с большим количеством лишних тестов для null, он выдает много предупреждений, поэтому его может быть сложно модифицировать. Но в нем также обнаружится много ошибок. Мне это нравится. Из-за этого мой код намного чище и надежнее.
(Существует также новый язык, который решает эту проблему. Kotlin, который компилируется в байтовый код Java, позволяет вам указать, может ли объект иметь значение null при его объявлении. Это более чистый подход.)
Дополнение к исходному сообщению (версия 2)
После долгих размышлений я неохотно пришел к выводу, что возвращать необязательный тип допустимо при одном условии: полученное значение на самом деле может быть null. Я видел много кода, в котором люди обычно возвращают необязательный тип из геттеров, которые никак не могут вернуть null. Я рассматриваю это как очень плохую практику кодирования, которая только усложняет код, что повышает вероятность ошибок. Но когда возвращаемое значение на самом деле может быть null, продолжайте и заключите его в необязательный.
Имейте в виду, что методы, предназначенные для функционального программирования и требующие ссылки на функцию, будут (и должны) быть записаны в двух формах, одна из которых использует Optional. Например, Optional.map()
и Optional.flatMap()
оба принимают ссылки на функции. Первый принимает ссылку на обычный геттер, а второй принимает тот, который возвращает необязательный. Таким образом, вы никому не делаете одолжения, возвращая необязательный тип, где значение не может быть null.
Сказав все это, я по-прежнему считаю, что подход, используемый Nullness Checker, является лучшим способом борьбы с null, поскольку они превращают NullPointerExceptions из ошибок времени выполнения в ошибки времени компиляции.
Ответ 4
Я бы сказал, что в целом неплохо использовать необязательный тип для возвращаемых значений, которые могут быть обнуляемыми. Однако применительно к фреймворкам я предполагаю, что замена классических геттеров необязательными типами вызовет много проблем при работе с фреймворками (например, Hibernate), которые полагаются на соглашения о кодировании для геттеров и сеттеров.