Comments 20
Не со всеми примерами данного цикла статей я согласен. А вот с Brian Goetz (судя по приведенному высказыванию в переводе автора) — напротив.
- Optional не стоит злоупотреблять. Он точно не должен быть полем класса. Хотя бы из приведенных в статье соображений.
- Он уж точно не должен возвращаться геттерами полей класса. Геттеры должны быть прозрачными, а не плодить лишние обертки.
- Он не должен использоваться в прозрачных сеттерах полей.
- Его не надо использовать там, где это ведет к загромождению интерфейса и к увеличению объема кода со стороны пользователя. Яркий пример —
Map
-ы,List
-ы и им подобные. Ни в коем случае нельзя возвращатьOptional
на запрос значения по ключу/индексу, если пользователь явно не попросил вернуть именноOptional
.
Что касается значений по умолчанию, то использовать или нет здесь Optional
— весьма спорный вопрос. Классический вариант с null
, 0 или -1 чаще оказывается нагладнее и правильнее. Да, Optional
"безопаснее", т.е. он подстегивает программиста проверять, действительно ли значение есть, а также избавляет от магических чисел, но какой ценой… Ценой подсовывания вместо реального объекта обертки над ним! А это, знаете ли, протекание реализации в контракт метода, причем не самое хорошее. Возможность возврата или установки null
должна быть оговорена в контракте метода, но именно оговорена (задокументирована, зааннотирована), а не обернута в Optional
или еще что-то.
Что касается NullPointerException
(NPE в народе), то это вполне штатная ситуация времени разработки. Я считаю, что проверка на null
входных значений (сеттеров, методов, конструкторов) всегда должна присутствовать в явном или неявном виде. Для этого даже стандартных helper-метод есть. Бояться словить или бросить NPE не надо. Просто условия их возникновения или не возникновения, как ранее было сказано, должны присутствовать в контракте метода. И не в виде Optional
, поскольку это сбивает с толку, заставляет оборачивать аргументы в Optional
насильно, а также приводит к полной неразберихе, когда в старых кусках кода значения по умолчанию обозначается через null
, а в новых — уже гордо через Optional
. Кроме того, планирование интерфейсов усложняется. И тут может закрасться мысль вообще все оборачивать в Optional...
Вот в каких случаях использование Optional
действительно целесообразно и оправдано:
- Стримы.
- Методы-генераторы или источники данных. Например, получение (первого) объекта из БД по запросу. Здесь
Optional
будет выглядеть элегантнееnull
илиxxxOrDefault
. Про коллекции см. выше. - (Сомнительно и с опаской) Конструкторы и методы изменения состояния (в отличие от прозрачных сеттеров). См.
Socket
. Однако переусердствовать тоже не стоит. В прозрачные сеттеры и прозрачные конструкторы лучше все-таки по старинке передаватьnull
. А логику переключения состояния, если какой-то объект устанавливается вnull
/Optional.empty()
— по старинке обрабатывать отдельным методом или прописать поведение в контракте (что не всегда приемлемо).
Других возможностей его применения я не вижу. Возможно, пока. Кстати, что очень спорно, Optional
можно применить в коллекциях, которые не переваривают null
. Но это из разряда "приятный бонус" и к практике, вероятно, имеют мало отношения.
Других возможностей его применения я не вижу.
В джаве особых возможностей и нет.
В других языках, где это органично вписано в язык, испольуется очень широко. Kotlin: прямо в системе типов есть типы, которым нельзя присваивать null (кстати, обёртки там нет, никакого снижения производительности). Rust: нет нулевых указателей, но зато есть оптимизация, что Option с указателем — не обёртка, а просто указатель. Scala: Option реализована как обёртка, обладает кучей возможностей и интегрирована в стандартную библиотеку. Например, у всех коллекций есть методы и для возвращения обычных значений и для Option, можно использовать что хочется в зависимости от ситуации.
В том-то и дело, что в java это совершенно новая вещь, которая ни разу не интегрирована со стандартной библиотекой и тем более не имеет поддержки на уровне языка (боже упаси). Это, в общем-то, и вынуждает нас идти на некие уступки и не использовать повально Optional
везде, где это только можно, возможно, было бы удобно.
Кроме стримов он пока нигде не используется. Не использовался. До Java 9. С Java 9 он начал появляться в новых APIшках. Местами в старых. ServiceLoader, например, где он как раз и уместен — те самые "источники данных", о которых я упоминал ранее. Но в целом Optional
сейчас есть только в стримах и в фишках вокруг них.
Кроме того, методы типа or(...) позволяют (фактически) строить workflow примерно как BPM. Другими словами, ветвления потоков управления и данных можно было бы поддерживать на уровне синтаксиса. Но для этого больше подходят двухмерные текстовые структуры либо графика. А это уже слишком большой прыжок от нынешних реалий.
А вот поддержка на уровне языка (в виде расширения синтаксиса) может и была бы интересна.
Я бы так не сказал. Посмотрите на C#. Во что он превратился. В него тащут все, что считают полезным, и это печально. Сложно сохранять парадигму языка, постоянно навешивая новые языковые конструкции, которые порой только ухудшают восприятие кода. Сокращая количество часов на ввод кода мы увеличиваем в несколько раз количество часов на последующий анализ этого кода нами самими и/или другими разработчиками. Получается какой-то зоопарк конструкций, где еще и совершенно разные по смыслу вещи могут обозначаются одинаково/слишком похоже в разных контекстах. Хотя не секрет, что не для всего это верно.
Но это в целом. Что же касается оператора Элвиса, не считаю конкретно его необходимым на уровне языка, даже при том, что он весьма удобен. И хотя он и удобен, мы теряем контроль над потоком управления, получаем дополнительную сложность в отладке, дополнительный оверхед при чтении таких конструкций, особенно в случае длинных цепочек вида a?.b.c?.d?:e
. Повторюсь, не стоит тащить все, что удобно, в язык, тем более такой консервативный язык, как java.
И хотя он и удобен, мы теряем контроль над потоком управления, получаем дополнительную сложность в отладке, дополнительный оверхед при чтении таких конструкций, особенно в случае длинных цепочек
Оператор Елвиса либо if..else, да ещё со скобками. Одно из двух необходимо применять. В простых ситуациях оператор Елвиса удобне, с моей точки зрения.
Но решение уже принято. Нового синтаксиса в связи с Optional в Java не добавили.
Про Kotlin и его не-нулевые типы. Насколько я понял, создатели Kotlin пытались за счёт введённой программистом информации проследить импользование переменной и уже на этапе компиляции найти места возникновения NPE. Дело безусловно очень хорошее. И вообще Kotlin содержит много полезных штук. И тем не менее — Optional — это не только и не столько предохранение от NPE, что я пытался показать на многочисленных приближенных к реальности примерах во второй и третьей статьях серии. Да и результаты опроса в конце первой статьи указывают, что многие программисты Java ценят полный спектр методов Optional.
Сравнение с Rust наверное не очень корректное, поскольку у Java и Rust разные целевые группы и базовые парадигмы.
Scala я знаю только в теории. Как я понимаю, именно оттуда взяты многие концепции для Optional в Java. А какие практически интересные возможности Option и его окрестностей в Scala представляются Вам остро недостающими в Java Optional?
Если что, я последнее время на джаве не писал. Могу ошибаться. Итак, ссылочки:
Часть неудобства при использовании связана с самим языком (тут уж ничего не поделать, не менять же весь язык ради одного класса). Например, метод orElse(new SomeObject())
в скале принимает лениво вычисляемое значение, в нём можно написать какую-нибудь логику типа создания нового элемента. Или, например, есть приятный синтаксис for (a <- option1; b <- option2) yield a+b
, благодаря которому удобно работать с несколькими Option одновременно.
Часть неудобств вызвана именно реализацией класса (вернее, отсутствием некоторой функциональности). Почему в скале Option Serializeble, а в джаве — нет? И ещё — в скале Option довольно сильно похожа на маленькую коллекцию — есть приятные методы типа foreach, exists, forall, contains
. С их помощью код становится лаконичнее. (В джаве похожая функциональность есть у стрима, в который можно превратить Optional, но мне такой способ кажется немного странным, тем более превращать его придётся вручную).
Ещё часть неудобств вызвана стандартной коллекцией. Например, я не могу в джаве от HashMap получить Option. Конечно, это всё мелочи, можно написать что-нибудь типа Optional.of(hashMap.getOrDefault(key, null)), но получится куча некрасивого кода.
В итоге получается, что код с использованием Option в скале выглядит коротким и зачастую более простым, чем с проверками на null, а в джаве — наоборот. И чтобы Option хорошо вписался в язык, придётся внести кучу изменений, которых делать, скорее всего, не будут.
Во вторых — сравнивать подходы непросто, не опробовав их интенсивно в реальных условиях. Да, Option в Scala содержит больше методов. Но так ли уж это хорошо?
Тут просматривается интересная диалектика. До появления Optional в Java лучшим его заменителем (т.е. способом решать проблемы, которые потом решились с помощью Optional) считался список из нуля или строго одного элемента. Об этом я писал в первой статье серии. Это решение неудолетворительное, поскольку фактически мы вводим новое понятие специального списка, но технически используем полноценный список. Многие методы из List, например, никогда не применимы к усечённому списку для представления потенциально нулевых обьектов.
В Scala Martin Odersky взял и расширил понятие футляра или контейнера некоторыми методами характерными для списков и итераторов. Тем самым почти придя к ситуации в Java до появления Optional. Вот такая диалектика.
Другими словами, мне кажется, что Option в Scala из чисто догматических соображений перегружен методами, которые технически работают, но концептуально чужды идее футляра или контейнера, в котором может содержаться не более чем один обьект.
И всё-таки… класс Optional очень сильно отличается от очень многих других классов в Java. Тут начинается магия программирования. Аналогия с физикой. В уравнении Шрёдингера всего 8 символов, а про него написано много-много статей и книг. В классе Optional всего сто строк кода, а про него написаны уже сотни статей. Может тысячи.
И эта магия меняет менталитет. Я сам вначале его использовал очень осторожно, потом преувеличенно, а сейчас на основании описанных в статье рекомендаций.
Хотя разумеется, эти рекомендации надо адаптировать к условиям каждого конкретного проекта. Наприме, Вы абсолютно правы, в старый код вряд ли следует внедрять Optional.
Проблема создателей продуктов для массового пользователя состоит в том, что трудно оценить- будут ли продукт использовать. И если да — то так ли, как задумывали авторы.
А среди миллионов программистов всегда найдутся довольные, недовольные и «Левши», которые сумеют подковать блоху. Хоть она после этого и перестанет прыгать.
О том, как они отбирают и оценивают новые фичи, Brain Goetz относительно недавно рассказал в InfoQ: www.infoq.com/news/2018/02/data-classes-for-java
Цикл статей интересный, подкинул пищи для размышлений.
Возможно, Вы хотели написать "объект" в предложении "… передаваемый МЕТОД никогда не будет равен null."?
Объект в футляре или Optional в Java 8 и Java 9: Часть 5: Недосказанное и постер в подарок