Зачем использовать Optional в Java 8+ вместо традиционных проверок нулевых указателей?

110

Недавно мы перешли на Java 8. Теперь я вижу приложения, заполненные Optionalобъектами.

До Java 8 (Стиль 1)

Employee employee = employeeServive.getEmployee();

if(employee!=null){
    System.out.println(employee.getId());
}

После Java 8 (Стиль 2)

Optional<Employee> employeeOptional = Optional.ofNullable(employeeService.getEmployee());
if(employeeOptional.isPresent()){
    Employee employee = employeeOptional.get();
    System.out.println(employee.getId());
}

Я не вижу добавленной стоимости, Optional<Employee> employeeOptional = employeeService.getEmployee();когда сама служба возвращает необязательно:

Исходя из фона Java 6, я вижу Стиль 1 как более понятный и с меньшим количеством строк кода. Есть ли какое-то реальное преимущество, которого мне здесь не хватает?

Консолидирован от понимания всех ответов и дальнейших исследований в блоге

user3198603
источник
20
Oracle имеет обширную статью об использовании Optional .
Майк Партридж
28
Тьфу! employeeOptional.isPresent()кажется, полностью упускает из виду типы опций. Согласно комментарию @ MikePartridge, это не рекомендуется или идиоматично. Явная проверка, является ли тип параметра чем-то или ничем, является нет-нет. Вы должны просто mapили flatMapнад ними.
Андрес Ф.
7
См Stack Overflow Ответ на от Брайана Гетца , в Java Language Architect в Oracle. Optional
Василий Бурк
6
Это то, что происходит, когда вы выключаете свой мозг во имя «лучших практик».
1918 года
9
Это довольно плохое использование опционально, но даже это имеет свои преимущества. При нулевых значениях все может быть нулевым, так что вам нужно постоянно проверять, необязательно вызывать, что эта конкретная переменная, вероятно, отсутствует, убедитесь, что вы проверили ее
Ричард Тингл

Ответы:

108

Стиль 2 не подходит для Java 8, чтобы увидеть все преимущества. Вы не хотите if ... useвообще. Смотрите примеры Oracle . Принимая их советы, мы получаем:

Стиль 3

// Changed EmployeeServive to return an optional, no more nulls!
Optional<Employee> employee = employeeServive.getEmployee();
employee.ifPresent(e -> System.out.println(e.getId()));

Или более длинный фрагмент

Optional<Employee> employee = employeeServive.getEmployee();
// Sometimes an Employee has forgotten to write an up-to-date timesheet
Optional<Timesheet> timesheet = employee.flatMap(Employee::askForCurrentTimesheet); 
// We don't want to do the heavyweight action of creating a new estimate if it will just be discarded
client.bill(timesheet.orElseGet(EstimatedTimesheet::new));
Caleth
источник
19
на самом деле мы запрещаем большинство применений isPresent (пойманных при проверке кода)
jk.
10
@jk. действительно, если бы произошла перегрузка, void ifPresent(Consumer<T>, Runnable)я бы поспорил о полном запрете isPresentиget
Caleth
10
@Caleth: Вы можете быть заинтересованы в Java 9 - х ifPresentOrElse(Consumer<? super T>, Runnable).
wchargin
9
Я нахожу один недостаток в этом стиле java 8 по сравнению с java 6: он обманывает инструменты покрытия кода. С оператором if он показывает, когда вы пропустили ветку, а не здесь.
Тьерри
14
@ Аарон "резко снижает читабельность кода". Какая часть точно снижает читабельность? Больше похоже на «я всегда делал это по-другому, и я не хочу менять свои привычки», чем на объективные рассуждения.
Во
47

Если вы используете Optionalв качестве слоя «совместимости» старый API, который все еще может возвращаться null, может быть полезно создать (не пустой) Optional на последнем этапе, когда вы уверены, что у вас что-то есть. Например, где вы написали:

Optional<Employee> employeeOptional = Optional.ofNullable(employeeService.getEmployee());
if(employeeOptional.isPresent()){
    Employee employeeOptional= employeeOptional.get();
    System.out.println(employee.getId());
}

Я бы выбрал:

Optional.of(employeeService)                 // definitely have the service
        .map(EmployeeService::getEmployee)   // getEmployee() might return null
        .map(Employee::getId)                // get ID from employee if there is one
        .ifPresent(System.out::println);     // and if there is an ID, print it

Дело в том, что вы знаете, что есть служба , отличная от NULL , поэтому вы можете завершить ее Optionalс помощью Optional.of(). Затем, когда вы звоните getEmployee()по этому поводу, вы можете или не можете получить сотрудника. Этот сотрудник может (или, возможно, не может) иметь удостоверение личности. Затем, если у вас есть идентификатор, вы хотите его распечатать.

Там нет необходимости явно проверить любой нуль, наличие и т.д., в этом коде.

Джошуа Тейлор
источник
4
В чем преимущество использования (...).ifPresent(aMethod)более if((...).isPresent()) { aMethod(...); }? Кажется, это просто еще один синтаксис для выполнения почти той же операции.
Руслан
2
@Ruslan Зачем использовать один специальный синтаксис для одного конкретного вызова, если у вас уже есть общее решение, которое одинаково хорошо работает для всех случаев? Мы просто применяем ту же идею с карты и здесь.
Во
1
@Ruslan ... API возвращает пустые значения вместо пустых коллекций или массивов. Например, вы можете сделать что - то вроде (предполагается , что класс Parent , для которых GetChildren () возвращает набор детей, или нуль , если нет детей): Set<Children> children = Optional.of(parent).map(Parent::getChildren).orElseGet(Collections::emptySet); Теперь я могу обрабатывать childrenравномерно, например, children.forEach(relative::sendGift);. Они могут даже быть объединены Optional.of(parent).map(Parent::getChildren).orElseGet(Collections::emptySet).forEach(relative::sendGift);. Весь шаблон проверки нуля и т. Д.,
Джошуа Тейлор
1
@Ruslan ... делегирован проверенному и верному коду в стандартной библиотеке. Это уменьшает количество кода, который я, как программист, должен писать, тестировать, отлаживать и т. Д.
Джошуа Тейлор
1
Рад, что пользуюсь Swift. если let employee = employeeService.employee {print (employee.Id); }
gnasher729
23

Существует почти нет добавленной стоимости в наличии Optionalодного значения. Как вы видели, это просто заменяет проверку на nullпроверку на наличие.

Существует огромное преимущество в том, чтобы иметь Collectionтакие вещи. Все методы потоковой передачи, введенные для замены низкоуровневых циклов старого стиля, понимают Optionalвещи и делают с ними правильные вещи (не обрабатывают их или, в конечном счете, возвращают еще одно незаданное Optional). Если вы имеете дело с n предметами чаще, чем с отдельными предметами (а 99% всех реалистичных программ это делают), то иметь встроенную поддержку для опционально представляемых вещей - огромное преимущество. В идеальном случае вам никогда не придется проверять наличие null или наличие.

Килиан Фот
источник
24
Помимо коллекций / потоков, Факультативный также большой , чтобы сделать Apis более самодокументируемыми, например , Employee getEmployee()против Optional<Employee> getEmployee(). Хотя технически оба могут вернуться null, один из них может вызвать проблемы.
Амон
16
В дополнительном одном значении есть много ценности. Быть способным .map()без необходимости проверять на ноль все это прекрасно. Например, Optional.of(service).map(Service::getEmployee).map(Employee::getId).ifPresent(this::submitIdForReview);.
Джошуа Тейлор
9
Я не согласен с вашим первоначальным комментарием без добавленной стоимости. Необязательно предоставляет контекст для вашего кода. Беглый взгляд может сказать вам правильность функции, и все случаи покрыты. Принимая во внимание, что при нулевой проверке следует ли проверять ноль каждый тип ссылки на каждый метод? Аналогичный случай может быть применен к классам и модификаторам public / private; они добавляют нулевое значение в ваш код, верно?
Артс
3
@JoshuaTaylor Честно говоря, по сравнению с этим выражением я предпочитаю старомодный чек на null.
Килиан Фот
2
Еще одно большое преимущество Optional, даже с одиночными значениями, заключается в том, что вы не можете забыть проверить нулевое значение, когда это необходимо сделать. В противном случае очень легко забыть проверку и получить NullPointerException ...
Шон Бертон
17

Пока вы используете Optionalпросто как модный API для isNotNull(), то да, вы не найдете никаких различий только с проверкой null.

Вещи, которые вы НЕ должны использовать Optionalдля

OptionalBad Code ™ используется только для проверки наличия значения:

// BAD CODE ™ --  just check getEmployee() != null  
Optional<Employee> employeeOptional =  Optional.ofNullable(employeeService.getEmployee());  
if(employeeOptional.isPresent()) {  
    Employee employee = employeeOptional.get();  
    System.out.println(employee.getId());
}  

Вещи, которые вы должны использовать Optionalдля

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

Employee employee = Optional.ofNullable(employeeService.getEmployee())
                            .orElseGet(Employee::new);
System.out.println(employee.getId());

Или, если создание нового сотрудника слишком дорого:

Optional<Employee> employee = Optional.ofNullable(employeeService.getEmployee());
System.out.println(employee.map(Employee::getId).orElse("No employee found"));

Кроме того, чтобы все знали, что ваши методы могут не возвращать значение (если по какой-либо причине вы не можете вернуть значение по умолчанию, как описано выше):

// Your code without Optional
public Employee getEmployee() {
    return someCondition ? null : someEmployee;
}
// Someone else's code
Employee employee = getEmployee(); // compiler doesn't complain
// employee.get...() -> NPE awaiting to happen, devs criticizing your code

// Your code with Optional
public Optional<Employee> getEmployee() {
    return someCondition ? Optional.empty() : Optional.of(someEmployee);
}
// Someone else's code
Employee employee = getEmployee(); // compiler complains about incompatible types
// Now devs either declare Optional<Employee>, or just do employee = getEmployee().get(), but do so consciously -- not your fault.

И, наконец, есть все потоковые методы, которые уже были объяснены в других ответах, хотя на самом деле вы решили не использовать их, Optionalа использовать Optionalпредоставленные кем-то другим.

Walen
источник
1
@ Kapep Действительно. Мы провели эту дискуссию на / r / java, и я сказал : «Я вижу Optionalупущенную возможность. «Вот, сделайте эту новую Optionalвещь, которую я сделал, чтобы вы были вынуждены проверять нулевые значения. О, и, кстати ... Я добавил get()метод, чтобы вам на самом деле не нужно ничего проверять;);)» , (...) Optionalхорош как неоновая вывеска «ЭТО МОЖЕТ БЫТЬ НУЛЬ», но само существование его get()метода наносит ему вред » . Но OP спрашивал причины использования Optional, а не причины против этого или недостатки дизайна.
Вален
1
Employee employee = Optional.ofNullable(employeeService.getEmployee());В вашем третьем фрагменте не должно быть типа Optional<Employee>?
Чарли Хардинг
2
@immibis Каким образом калека? Основная причина использования getзаключается в том, что вам нужно взаимодействовать с каким-либо устаревшим кодом. Я не могу придумать много веских причин в новом коде когда-либо использовать get. Тем не менее, при взаимодействии с унаследованным кодом часто не существует альтернативы, но, тем не менее, Уален считает, что существование getпобуждает новичков писать плохой код.
Во
1
@immibis Я ни Mapразу не упомянул, и я не знаю никого, кто бы делал такие радикальные изменения, не совместимые с предыдущими версиями , поэтому уверен, что вы можете намеренно создавать плохие API-интерфейсы Optional- не уверен, что это доказывает. Это Optionalимело бы смысл для некоторого tryGetметода все же.
Во
2
@ Voo Map- это пример, с которым все знакомы. Конечно, они не могут сделать Map.getвозвращение Optional из-за обратной совместимости, но вы можете легко представить другой класс, похожий на Map, у которого не было бы таких ограничений совместимости. Скажи class UserRepository {Optional<User> getUserDetails(int userID) {...}}. Теперь вы хотите получить данные пользователя, вошедшего в систему, и знаете, что они существуют, потому что им удалось войти в систему, и ваша система никогда не удаляет пользователей. Так и делаешь theUserRepository.getUserDetails(loggedInUserID).get().
immibis
12

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

//service 1
Optional<Employee> employeeOptional = employeeService.getEmployee();
if(employeeOptional.isPresent()){
    Employee employeeOptional= employeeOptional.get();
    System.out.println(employee.getId());
}

//service 2
Employee employee = employeeService.getEmployeeXTPO();
System.out.println(employee.getId());
Родриго Менезес
источник
8
Меня сбивает с толку, что все изящные функциональные вещи с опциями, хотя они действительно хороши, считаются более важными, чем этот момент. До версии Java 8 вам приходилось копаться в Javadoc, чтобы узнать, нужно ли проверять нулевой ответ на вызов. Опубликовать Java 8, вы знаете из типа возврата, если вы можете получить «ничего» обратно или нет.
Буб
3
Что ж, есть проблема "старого кода", когда все еще может возвращаться ноль ... но да, возможность отличить подпись - это здорово.
Хакон Лотвейт
5
Да, это, безусловно, лучше работает в языках, где необязательный <T> - единственный способ выразить, что значение может отсутствовать, то есть в языках без нулевого ptr.
Dureuill
8

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

Это привело к тому, что вы пишете совершенно разборчивый код, который кажется вполне разумным, но вас соблазнили мерзкие двойники-соблазнители, которые получают и представляют.

Конечно, быстро возникает вопрос: «Почему они присутствуют и даже там?»

Многие люди здесь упускают из виду то, что isPresent () состоит в том, что это не то, что сделано для нового кода, написанного полностью занятыми людьми с тем, насколько чертовски полезны лямбды и кому нравится функциональность.

Это, однако, дает нам несколько (два) хороших, великих, гламурных (?) Преимуществ:

  • Это облегчает переход унаследованного кода на использование новых функций.
  • Это облегчает изучение кривых Факультативного.

Первый довольно прост.

Представьте, что у вас есть API, который выглядел так:

public interface SnickersCounter {
  /** 
   * Provides a proper count of how many snickers have been consumed in total.
   */
  public SnickersCount howManySnickersHaveBeenEaten();

  /**
    * returns the last snickers eaten.<br>
    * If no snickers have been eaten null is returned for contrived reasons.
    */
  public Snickers lastConsumedSnickers();
}

И у вас был унаследованный класс, использующий это как таковой (заполните пробелы):

Snickers lastSnickers = snickersCounter.lastConsumedSnickers();
if(null == lastSnickers) {
  throw new NoSuchSnickersException();
}
else {
  consumer.giveDiabetes(lastSnickers);
}

Придуманный пример, чтобы быть уверенным. Но потерпите меня здесь.

Java 8 уже запущена, и мы карабкаемся, чтобы попасть на борт. Таким образом, мы хотим заменить наш старый интерфейс чем-то, что возвращает Optional. Почему? Потому что, как кто-то другой уже любезно упомянул: это снимает догадки о том, может ли что-то быть нулевым. Это уже было указано другими. Но сейчас у нас проблема. Представьте, что у нас есть (извините, когда я нажимаю alt + F7 невинного метода) 46 мест, где этот метод вызывается в хорошо проверенном унаследованном коде, который отлично работает в противном случае. Теперь вы должны обновить все это.

Это где настоящее сияет.

Потому что сейчас: Snickers lastSnickers = snickersCounter.lastConsumedSnickers (); if (null == lastSnickers) {throw new NoSuchSnickersException (); } else {consumer.giveDiabetes (lastSnickers); }

будет выглядеть так:

Optional<Snickers> lastSnickers = snickersCounter.lastConsumedSnickers();
if(!lastSnickers.isPresent()) {
  throw new NoSuchSnickersException();
}
else {
  consumer.giveDiabetes(lastSnickers.get());
}

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

Обратите внимание, что это означает, что то, как вы это сделали, по сути является способом работы с устаревшим кодом без дорогостоящих переписываний. Так что насчет нового кода?

Ну, в вашем случае, когда вы просто хотите что-то напечатать, вы просто сделаете:

. SnickersCounter.lastConsumedSnickers () ifPresent (System.out :: Println);

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

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

Если вы хотите использовать Optional в качестве простой проверки нулевой безопасности, вам нужно просто выполнить следующее:

new Optional.ofNullable(employeeServive.getEmployee())
    .map(Employee::getId)
    .ifPresent(System.out::println);

Конечно, красивая версия выглядит так:

employeeService.getEmployee()
    .map(Employee::getId)
    .ifPresent(System.out::println);

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

Это, конечно, очень простой пример, в котором легко понять все, что мы пытаемся сделать. Это не всегда так просто в реальной жизни. Но обратите внимание, что в этом примере мы выражаем наши намерения. Мы хотим ПОЛУЧИТЬ сотрудника, ПОЛУЧИТЬ его удостоверение личности и, если возможно, распечатать его. Это вторая большая победа с Факультативным. Это позволяет нам создавать более понятный код. Я также думаю, что создание таких вещей, как создание метода, который делает кучу вещей, чтобы вы могли передать их на карту, в общем, хорошая идея.

Хаакон Лотвейт
источник
1
Проблема здесь в том, что вы пытаетесь сделать так, чтобы эти функциональные версии были так же удобочитаемы, как и более старые версии, в одном случае даже было сказано, что пример «совершенно ясен». Это не относится к делу. Этот пример был понятен, но не совсем так, так как требует больше размышлений. map(x).ifPresent(f)просто не имеет такого же интуитивного смысла, как if(o != null) f(x)это. ifВерсия совершенно ясно. Это еще один случай, когда люди прыгают в популярном фургоне, а не в случае реального прогресса.
Аарон
1
Обратите внимание, что я не говорю, что нет никакой пользы в использовании функционального программирования или Optional. Я имел в виду только использование необязательного в данном конкретном случае и, более того, читабельность, поскольку несколько человек действуют так, что этот формат одинаково или более удобочитаем, чем if.
Аарон
1
Это зависит от наших представлений о ясности. Хотя вы делаете очень хорошие замечания, я хотел бы также отметить, что для многих людей лямбды были новыми и странными в какой-то момент. Я бы осмелился сделать ставку на интернет-куки-файл, который многие люди называют isPresent, и он почувствовал восхитительное облегчение: теперь они могли бы обновить устаревший код и затем изучить лямбда-синтаксис позже. С другой стороны, люди с Lisp, Haskell или похожим языковым опытом, вероятно, были в восторге от того, что теперь они могут применять знакомые паттерны к своим проблемам в Java ...
Haakon Løtveit
@ Аарон - ясность не является предметом дополнительных типов. Я лично нахожу, что они не уменьшают ясность, и в некоторых случаях (хотя и не в этом случае) они увеличивают его, но ключевое преимущество заключается в том, что (при условии, что вы избегаете использования isPresentи getнасколько это реально возможно), они улучшить безопасность . Труднее написать неправильный код, который не может проверить отсутствие значения, если тип является типом, Optional<Whatever>а не обнуляемым Whatever.
Жюль
Даже если вы сразу же извлекаете значения, если вы их не используетеget , вы вынуждены выбирать, что делать, когда происходит пропущенное значение: вы либо используете orElse()(или orElseGet()), чтобы предоставить значение по умолчанию, либо orElseThrow()выбросить выбранное значение. исключение, которое документирует природу проблемы , которая лучше, чем общий NPE, который не имеет смысла, пока вы не копаетесь в трассировке стека.
Жюль
0

Я не вижу преимущества в стиле 2. Вы все еще должны понимать, что вам нужна нулевая проверка, и теперь она еще больше, а значит, менее читабельна.

Лучший стиль был бы, если employeeServive.getEmployee () вернул бы Optional, и тогда код стал бы:

Optional<Employee> employeeOptional = employeeServive.getEmployee();
if(employeeOptional.isPresent()){
    Employee employee= employeeOptional.get();
    System.out.println(employee.getId());
}

Таким образом, вы не можете вызвать callemployeeServive.getEmployee (). GetId (), и вы заметили, что вам следует каким-то образом обрабатывать необязательные значения. И если во всей вашей кодовой базе методы никогда не вернут null (или почти никогда с хорошо известными вашей команде исключениями из этого правила), это повысит безопасность от NPE.

user470365
источник
12
Опционально становится бессмысленным осложнением в момент, когда вы используете isPresent(). Мы используем опционально, поэтому нам не нужно тестировать.
candied_orange
2
Как уже говорили другие, не используйте isPresent(). Это неправильный подход к опциям. Используйте mapили flatMapвместо.
Андрес Ф.
1
@CandiedOrange И, тем не менее, голос с наибольшим количеством голосов (говорят, чтобы использовать ifPresent) это просто еще один способ проверить.
иммибис
1
@CandiedOrange ifPresent(x)- это такой же тест, как и if(present) {x}. Возвращаемое значение не имеет значения.
Immibis
1
@CandiedOrange Тем не менее, вы изначально сказали «так что нам не нужно тестировать». Вы не можете сказать «так что нам не нужно проверять», когда вы, на самом деле, все еще проверяете ... что вы и делаете.
Аарон
0

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

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

Однако, чтобы это работало, вы должны последовательно применять этот шаблон.

санки
источник
1
Простой способ для достижения этой цели используется @Nullableи @ReturnValuesAreNonnullByDefaultвместе со статическим анализом.
Maaartinus
@maaartinus Добавление дополнительных инструментов в виде статического анализа и документации в аннотациях декоратора, проще, чем поддержка API времени компиляции?
Сани
Добавление дополнительных инструментов действительно проще, так как не требует никаких изменений кода. Кроме того, вы хотите иметь в любом случае, так как он ловит и другие ошибки. +++ Я написал тривиальный сценарий добавления package-info.javaсо @ReturnValuesAreNonnullByDefaultвсеми моими пакетами, поэтому все, что мне нужно сделать, это добавить, @Nullableкуда вы должны переключиться Optional. Это означает меньшее количество символов, отсутствие добавленного мусора и никаких накладных расходов во время выполнения. Мне не нужно искать документацию, как показано при наведении указателя мыши на метод. Инструмент статического анализа улавливает ошибки и последующие изменения обнуляемости.
Maaartinus
OptionalБольше всего меня беспокоит то, что это добавляет альтернативный способ борьбы с обнуляемостью. Если это было в Java с самого начала, это может быть хорошо, но сейчас это просто боль. Идти по пути Котлина было бы ИМХО намного лучше. +++ Обратите внимание, что List<@Nullable String>по-прежнему список строк, а List<Optional<String>>нет.
Maaartinus
0

Мой ответ: просто не надо. По крайней мере, дважды подумайте, действительно ли это улучшение.

Выражение (взято из другого ответа) как

Optional.of(employeeService)                 // definitely have the service
        .map(EmployeeService::getEmployee)   // getEmployee() might return null
        .map(Employee::getId)                // get ID from employee if there is one
        .ifPresent(System.out::println);     // and if there is an ID, print it

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

Это выглядит лучше, чем в старом стиле

Employee employee = employeeService.getEmployee();
if (employee != null) {
    ID id = employee.getId();
    if (id != null) {
        System.out.println(id);
    }
}

что делает очевидным, что могут быть elseоговорки.

Функциональный стиль

  • выглядит совершенно иначе (и нечитаемо для людей, не привыкших к нему)
  • это боль отлаживать
  • не может быть легко расширен для обработки «отсутствующего» случая ( orElseне всегда достаточно)
  • вводит в заблуждение игнорированием «отсутствующего» дела

Ни в коем случае его не следует использовать как панацею. Если это было универсально применимо, то семантика .оператора должна быть заменена семантикой ?.оператора , NPE забыт и все проблемы проигнорированы.


Будучи большим поклонником Java, я должен сказать, что функциональный стиль - просто замена плохого человека на Java.

employeeService
.getEmployee()
?.getId()
?.apply(id => System.out.println(id));

хорошо известен из других языков. Согласны ли мы с этим утверждением?

  • не является (действительно) функциональным?
  • очень похоже на код старого стиля, просто намного проще?
  • гораздо удобнее для чтения, чем функциональный стиль?
  • подходит для отладчика?
maaartinus
источник
Похоже, вы описываете реализацию этой концепции в C # с помощью функции языка, которая полностью отличается от реализации Java с классом базовой библиотеки. Они выглядят одинаково для меня
Caleth
@Caleth Ни в коем случае. Мы могли бы говорить о «концепции упрощения нулевой оценки», но я бы не назвал ее «концепцией». Можно сказать, что Java реализует то же самое, используя базовый класс библиотеки, но это просто беспорядок. Просто начните с employeeService.getEmployee().getId().apply(id => System.out.println(id));и сделайте его нуль-безопасным один раз с помощью оператора безопасной навигации и один раз с помощью Optional. Конечно, мы можем назвать это «деталью реализации», но реализация имеет значение. Есть случаи, когда lamdas делают код проще и приятнее, но здесь это просто уродливое злоупотребление.
Maaartinus
Библиотека классов: Optional.of(employeeService).map(EmployeeService::getEmployee).map(Employee::getId).ifPresent(System.out::println);функция Язык: employeeService.getEmployee()?.getId()?.apply(id => System.out.println(id));. Два синтаксиса, но в итоге они выглядят очень похоже на меня. И "концепция" это также Optionalизвестный NullableкакMaybe
Caleth
Я не понимаю, почему вы думаете, что одно из них - "уродливое насилие", а другое - хорошо. Я могу понять, что ты думаешь, что оба "безобразно оскорбляют" или оба хороши.
Caleth
@Caleth Одно из различий состоит в том, что вместо добавления двух вопросительных знаков, вам придется переписать все это. Проблема не в том, что функция Language и коды классов библиотеки сильно различаются. Это нормально. Проблема в том, что исходный код и коды классов библиотеки сильно различаются. Та же идея, другой код. Это очень плохо. +++То, что подвергается насилию, - это лямды для обработки обнуляемости Ламды в порядке, но Optionalэто не так. Nullability просто нуждается в правильной языковой поддержке, как в Kotlin. Другая проблема: List<Optional<String>>не связана с тем List<String>, где нам нужно, чтобы они были связаны.
Maaartinus
0

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

Петрас Перлис
источник