Почему используется необязательная преференция для проверки нуля переменной?

25

Возьмите два примера кода:

if(optional.isPresent()) {
    //do your thing
}

if(variable != null) {
    //do your thing
}

Насколько я могу судить, самое очевидное отличие состоит в том, что Optional требует создания дополнительного объекта.

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

Уильям Данн
источник
4
Вы почти никогда не хотите использовать isPresent, обычно вы должны использовать ifPresent или map
jk.
6
@jk. Потому что ifутверждения оооочень последнее десятилетие, и теперь все используют монадные абстракции и лямбды.
user253751
2
@immibis Однажды у меня были долгие споры на эту тему с другим пользователем. Дело в том, что if(x.isPresent) fails_on_null(x.get)вы выходите из системы типов и должны гарантировать, что код не сломается «в вашей голове» на (по общему признанию) коротком расстоянии между условием и вызовом функции. В optional.ifPresent(fails_on_null)системе типов это гарантия для вас, и вам не о чем беспокоиться.
Ziggystar
1
Основным недостатком Java при использовании Optional.ifPresent(и различных других конструкций Java) является то, что вы можете эффективно изменять только конечные переменные и не можете генерировать проверенные исключения. Этого ifPresent, к сожалению, достаточно, чтобы часто избегать .
Майк

Ответы:

19

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

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

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

К сожалению, большинство языков, которые сейчас предлагаются Optional, не упразднены null, поэтому вы можете извлечь выгоду только из концепции, введя строгую политику «абсолютно нет null, никогда». Поэтому, Optionalнапример, Java не так убедительна, как в идеале.

Килиан Фот
источник
Поскольку ваш ответ является лучшим, возможно, стоит добавить использование Lambdas в качестве преимущества - для тех, кто читает это в будущем (см. Мой ответ)
Уильям Данн
Большинство современных языков предоставляют необнуляемые типы, Kotlin, Typescript, AFAIK.
Дзен
5
IMO, это лучше обрабатывается аннотацией @Nullable. Нет причин использовать Optionalтолько для напоминания о том, что значение может быть нулевым
Hilikus
18

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

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

return optional.flatMap(x -> x.anotherOptionalStep())
               .flatMap(x -> x.yetAnotherOptionalStep())
               .orElse(defaultValue);

С nullмне пришлось бы проверить трижды , nullпрежде чем продолжить, что добавляет много сложности и техническое обслуживание головных болей к коду. Optionalsесть этот чек , встроенный в flatMapи orElseфункции.

Заметьте, я не звонил ни isPresentразу, что вы должны воспринимать как запах кода при использовании Optionals. Это не обязательно означает, что вы никогда не должны использовать isPresent, просто вы должны тщательно изучить любой код, который делает, чтобы увидеть, есть ли лучший способ. В противном случае, вы правы, вы получаете только минимальную выгоду безопасности по сравнению с использованием null.

Также обратите внимание, что я не так беспокоюсь о том, чтобы заключить все это в одну функцию, чтобы защитить другие части моего кода от нулевых указателей от промежуточных результатов. .orElse(defaultValue)Например, если имеет смысл использовать my в другой функции, у меня гораздо меньше проблем с ее размещением, и гораздо проще составлять операции между различными функциями по мере необходимости.

Карл Билефельдт
источник
10

Это подчеркивает возможность нулевого значения в качестве действительного ответа, который люди часто предполагают (правильно или неправильно) не будет возвращен. Выделение тех немногих случаев, когда null действителен, позволяет исключить засорение вашего кода огромным количеством бессмысленных нулевых проверок.

В идеале установка переменной в null была бы ошибкой времени компиляции где угодно, но не обязательно; устранение исключений нулевого указателя во время выполнения. Очевидно, что обратная совместимость исключает это, к сожалению

Ричард Тингл
источник
4

Позволь мне привести пример:

class UserRepository {
     User findById(long id);
}

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

Второй пример:

class UserRepository {
     Optional<User> findById(long id);
}

Теперь, что здесь происходит, если нет пользователя с данным идентификатором? Правильно, вы получаете экземпляр Optional.absent () и можете проверить его с помощью Optional.isPresent (). Подпись метода с помощью Optional более подробно описывает его контракт. Сразу понятно, как должен вести себя метод.

Это также имеет преимущество при чтении клиентского кода:

User user = userRepository.findById(userId).get();

Я натренировал свой глаз, чтобы увидеть .get () как непосредственный запах кода. Это означает, что клиент намеренно игнорирует контракт вызванного метода. Это гораздо легче увидеть, чем без Optional, потому что в этом случае вы не сразу понимаете, что такое контракт вызываемого метода (допускает ли он нулевое значение или нет при возврате), поэтому вам нужно сначала проверить его.

Необязательный используется с соглашением о том, что методы с возвращаемым типом Необязательный никогда не возвращает нуль, и обычно также с (менее строгим) соглашением, что метод без необязательного возвращаемого типа никогда не возвращает ноль (но лучше аннотировать его с помощью @Nonnull, @NotNull или аналогичных) ,

QBD
источник
3

An Optional<T>позволяет вам иметь «отказ» или «отсутствие результата» в качестве допустимого значения ответа / возврата для ваших методов (подумайте, например, о поиске в базе данных). Использование Optionalвместо того, nullчтобы указывать сбой / отсутствие результата, имеет некоторые преимущества:

  • Это ясно говорит о том, что «провал» является вариантом. Пользователь вашего метода не должен угадывать, nullможет ли быть возвращен.
  • Значение null, по крайней мере, на мой взгляд, не должно использоваться для обозначения чего-либо, поскольку семантика может быть не совсем понятной. Он должен использоваться, чтобы сообщить сборщику мусора, что ранее упомянутый объект может быть собран.
  • OptionalКласс предоставляет множество методов приятно условно работу со значениями (например, ifPresent()) или определить значения по умолчанию прозрачным образом ( orElse()).

К сожалению, это не полностью устраняет необходимость nullпроверок в коде, поскольку сам Optionalобъект все еще может быть null.

pansen
источник
4
Это не совсем то, что необязательно для. Чтобы сообщить об ошибке, используйте исключение. Если вы не можете сделать это, используйте тип, который содержит либо результат, либо код ошибки .
Джеймс Янгман
Я категорически не согласен. Optional.empty () - отличный способ показать неудачу или несуществование, на мой взгляд.
LegendLength
@ LegendLength, я должен категорически не согласиться в случае неудачи. Если функция дает сбой, лучше дать мне объяснение, а не просто пустое «Я потерпел неудачу»
Уинстон Эверт,
Извините, я согласен, я имею в виду «ожидаемый сбой», когда не находил сотрудника с именем «x» во время поиска.
LegendLength
3

В дополнение к другим ответам, другое важное преимущество Optionals в написании чистого кода состоит в том, что вы можете использовать лямбда-выражение в случае наличия значения, как показано ниже:

opTr.ifPresent(tr -> transactions.put(tr.getTxid(), tr));
Уильям Данн
источник
2

Рассмотрим следующий метод:

void dance(Person partner)

Теперь давайте посмотрим на код вызова:

dance(null);

Это может привести к потенциально трудным для отслеживания сбоям в другом месте, потому что danceне ожидал нулевого значения.

dance(optionalPerson)

Это вызывает ошибку компиляции, потому что танец ожидал PersonнеOptional<Person>

dance(optionalPerson.get())

Если у меня не было человека, это вызывает исключение в этой строке, когда я незаконно пытался преобразовать Optional<Person>человека. Ключ в том, что в отличие от пропуска нулевого значения, кроме следов здесь, а не какого-то другого места в программе.

Чтобы сложить все вместе: неправильное использование опционального облегчает отслеживание проблем, неправильное использование нулевого вызывает трудное отслеживание проблем.

Уинстон Эверт
источник
1
Если вы вызываете исключения при вызове Optional.get()плохо написанного кода - вы почти никогда не должны вызывать Optional.get без предварительной проверки Optional.isPresent()(как указано в OP), иначе вы можете написать optionalPersion.ifPresent(myObj::dance).
Крис Купер
@QmunkE, да, вам следует вызывать Optional.get () только в том случае, если вы уже знаете, что option не пуст (либо потому, что вы вызвали isPresent, либо потому, что ваш алгоритм это гарантирует). Тем не менее, я беспокоюсь о людях, которые слепо проверяют isPresent, а затем игнорируют отсутствующие значения, создавая множество скрытых сбоев, которые будет трудно отследить.
Уинстон Эверт
1
Мне нравятся опционные Но я скажу, что простые старые нулевые указатели очень редко являются проблемой в реальном коде. Они почти всегда терпят неудачу рядом с оскорбительной строкой кода. И я думаю, что это преувеличивают люди, когда говорят на эту тему.
LegendLength
@ LegendLength, мой опыт показывает, что 95% случаев действительно легко проследить, чтобы определить, откуда исходит NULL. Однако оставшиеся 5% крайне сложно отследить.
Уинстон Эверт
-1

В Objective-C все ссылки на объекты являются необязательными. Из-за этого код, который вы опубликовали, глупый.

if (variable != nil) {
    [variable doThing];
}

Код, подобный приведенному выше, не слышен в Objective-C. Вместо этого программист просто:

[variable doThing];

Если variableсодержит значение, то doThingбудет вызываться по нему. Если нет, никакого вреда. Программа будет рассматривать его как неактивный и продолжит работу.

Это реальная выгода опционов. Вам не нужно засорять свой код if (obj != nil).

Даниэль Т.
источник
Разве это не просто молчаливая ошибка? В некоторых случаях вы можете позаботиться о том, чтобы значение было нулевым, и захотите что-то с этим сделать.
LegendLength
-3

if (option.isPresent ()) {

Очевидная проблема здесь состоит в том , что если « по желанию» на самом деле является отсутствует (т.е. равно нулю) , то ваш код будет взрывать с NullReferenceException (или аналогичный) пытается вызвать любой метод на объектную ссылку нулевой!

Возможно, вы захотите написать статический метод класса Helper, который определяет нулевое значение для любого данного типа объекта, что-то вроде этого:

function isNull( object o ) 
{
   return ( null == o ); 
}

if ( isNull( optional ) ) ... 

или, может быть, более полезно:

function isNotNull( object o ) 
{
   return ( null != o ); 
}

if ( isNotNull( optional ) ) ... 

Но являются ли какие-либо из них действительно более читаемыми / понятными / поддерживаемыми, чем оригинал?

if ( null == optional ) ... 
if ( null != optional ) ... 
Фил В.
источник