Навіщо використовувати необов’язково в 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 більш чітким і з меншою кількістю рядків коду. Чи є якась реальна перевага, якої мені тут не вистачає?

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



28
Гидота! employeeOptional.isPresent()Здається, повністю відсутня точка варіантів варіантів. Відповідно до коментаря @ MikePartridge, це не рекомендується і не є ідіоматичним. Явна перевірка того, чи є тип опції щось чи ні, є ні-ні. Ти повинен просто mapчи flatMapнад ними.
Андрес Ф.

7
Див Stack Overflow Відповідь на від Брайана Гетца , в Java Language Architect в Oracle. Optional
Василь Бурк

6
Це те, що відбувається, коли ви вимикаєте мозок від імені "найкращих практик".
іммібіс

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));

19
Дійсно, ми забороняємо більшість застосувань isPresent (зловив огляд коду)
jk.

10
@jk. Дійсно, якби було перевантаження, void ifPresent(Consumer<T>, Runnable)я б заперечував за повну заборону isPresentіget
Калет

10
@Caleth: Вас можуть зацікавити Java 9 ifPresentOrElse(Consumer<? super T>, Runnable).
wchargin

9
Я знаходжу один недолік у цьому стилі java 8, порівняно з java 6: він обдурює засоби покриття коду. Якщо оператор if, він показує, коли ви пропустили відділення, а не тут.
Тьєррі

14
@Aaron "різко знижує читабельність коду". Яка частина точно знижує читабельність? Звучить більше як "Я завжди робив це по-іншому, і не хочу змінювати звички", ніж об'єктивне міркування.
Ву

47

Якщо ви використовуєте Optionalшар "сумісності" між старішим API, який все ще може повернутися null, можливо, буде корисним створити (не порожній) необов'язковий на останньому етапі, щоб ви впевнені, що у вас щось є. Наприклад, де ви писали:

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

Справа в тому , що ви знаєте , що є непорожній співробітник служби , так що ви можете обернути , що в 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. якщо нехай співробітник = службовецьService.employee {print (службовець.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
Я не згоден з вашим початковим коментарем без доданої вартості. Необов’язково забезпечує контекст вашого коду. Швидкий погляд може сказати вам правильність функції, і всі випадки висвітлюються. Тоді як при нульовій перевірці ви повинні робити нульову перевірку кожного окремого типу посилань на кожен метод? Аналогічний випадок може бути застосований до класів та публічних / приватних модифікаторів; вони додають нульове значення у ваш код, правда?
ArTs

3
@JoshuaTaylor Чесно кажучи, по порівнянні з цим виразом, я вважаю за краще старомодний чек null.
Кіліан Фот

2
Ще одна велика перевага Факультативу навіть при одиничних значеннях полягає в тому, що ви не можете забути перевірити наявність нуля, коли вам слід це зробити. Інакше дуже просто забути чек і закінчити NullPointerException ...
Шон Бертон

17

Поки ви використовуєте так Optionalсамо, як фантазійний API для isNotNull(), тоді так, ви не знайдете жодних відмінностей, просто перевіривши null.

Речі, які НЕ слід використовувати Optionalдля

Використовуючи Optionalпросто для перевірки наявності значення Bad 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для

Уникайте, щоб значення не було, створивши його, коли це необхідно, використовуючи методи, що повертають нуль:

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наданими кимось іншим.


1
@Kapep Дійсно. У нас були ці дискусії на / r / java, і я сказав : «Я це бачу Optionalяк пропущену нагоду. "Тут є ця нова Optionalріч, яку я зробив, тому ви змушені перевіряти нульові значення. О, і до речі ... Я додав get()до неї метод, щоб вам насправді нічого не потрібно було перевіряти;);)" . (...) Optionalприємно, як неоновий знак "ЦЕ МОЖЕ БУТИ НУЛЬНО", але голе існування get()методу його калічить " . Але ОП просило причин використовувати Optional, а не причини чи недоліки в дизайні.
Валентин

1
Employee employee = Optional.ofNullable(employeeService.getEmployee());У вашому третьому фрагменті не повинен бути тип Optional<Employee>?
Чарлі Хардінг

2
@immibis Яким чином покалічений? Основна причина використання get- це якщо вам доводиться взаємодіяти зі старим кодом. Я не можу придумати багато причин у новому коді, який коли-небудь використовувати get. Однак, при взаємодії зі застарілим кодом часто немає альтернативи, але все-таки у Walen є думка, що існування getзмушує початківців писати поганий код.
Voo

1
@immibis Я жодного Mapразу не згадував, і мені не відомо, що хтось зробив такі різкі сумісні зміни, які не повертаються назад, тож переконайтесь, що ви могли навмисно створити погані API Optional- не впевнені, що це доводить. Було Optionalб сенс для якогось tryGetметоду.
Voo

2
@Voo Map- це приклад, з яким всі знайомі. Звичайно, вони не можуть зробити Map.getповернення необов'язковим через зворотну сумісність, але ви можете легко уявити інший клас, схожий на карту, який не мав би таких обмежень сумісності. Скажи class UserRepository {Optional<User> getUserDetails(int userID) {...}}. Тепер ви хочете отримати дані про користувача, і ви знаєте, що вони існують, оскільки їм вдалося увійти, і ваша система ніколи не видаляє користувачів. Так ви робите theUserRepository.getUserDetails(loggedInUserID).get().
іммібіс

12

Ключовим моментом у використанні необов’язкового є чітке розуміння, коли розробнику потрібно перевірити, повертають чи функцію значення чи ні.

//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 () - це те, що це не те, що створено для нового коду, написаного людьми повністю на борту з тим, наскільки корисні лямбди, які люблять готівку, і яким подобається функціональна річ.

Однак це дає нам кілька (двох) добрих, чудових, гламурних (?) Переваг:

  • Це полегшує перехід застарілого коду до використання нових функцій.
  • Це полегшує криві навчання за вибором.

Перший досить простий.

Уявіть, у вас є 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, і ми намагаємося вийти на борт. Отже, одна з речей, яку ми робимо, - це те, що ми хочемо замінити старий інтерфейс чимось, що повертає необов’язково. Чому? Тому що, як хтось уже милостиво згадував: Це виймає здогадки про те, чи може щось бути недійсним, на це вже вказували інші. Але зараз у нас є проблема. Уявіть, у нас є (вибачте, поки я натиснув alt + F7 невинним методом), 46 місць, де цей метод називається добре перевіреним спадковим кодом, який робить відмінну роботу інакше. Тепер ви повинні оновити все це.

ЦЕ це місце, де світить.

Тому що зараз: Snickers lastSnickers = snickersCounter.lastConsumedSnickers (); if (null == lastSnickers) {киньте новий 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 (). Вони там, що дозволяють механічно змінювати існуючий код, щоб використовувати новіші типи, не надто замислюючись про це. Отже, те, що ви робите, неправильно керується такими способами:

  • Ви викликаєте метод, який може повернути нуль. Правильна думка полягала б у тому, що метод повертає null.
  • Ви використовуєте застарілі методи бандайдів для боротьби з цим необов’язковим, замість того, щоб використовувати нові смачні методи, що містять лямбда-химерність.

Якщо ви хочете використовувати Необов’язково як просту перевірку безпеки на нуль, то, що ви повинні зробити, це просто:

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
Це залежить від наших понять ясності. Хоча ви робите дуже хороші моменти, я хотів би також зазначити, що для багатьох людей лямбди в якийсь момент були новими і дивними. Я б наважився зайнятись печивом в Інтернеті, що досить багато людей кажуть «Присутні» і отримують відчуття приємного полегшення: тепер вони можуть продовжувати оновлення застарілого коду, а потім пізнавати лямбда-синтаксис пізніше. З іншого боку, люди з Ліспом, Хаскеллом або подібним мовним досвідом, напевно, були в захваті від того, що вони тепер можуть застосовувати звичні зразки до своїх проблем у java ...
Haakon Løtveit

@Aaron - чіткість не є точкою Факультативних типів. Я особисто вважаю, що вони не знижують ясність, і є деякі випадки (хоча це не цей випадок), коли вони збільшують її, але ключовою перевагою є те, що (поки ви уникаєте використання isPresentі getнаскільки це реально можливо) вони підвищити безпеку . Важко написати неправильний код, який не може перевірити відсутність значення, якщо тип Optional<Whatever>замість використання нульового Whatever.
Жуль

Навіть якщо ви негайно виймаєте значення, поки ви не використовуєтеget , ви змушені вибирати, що робити, коли з’явиться відсутнє значення: ви б або використовували orElse()(або orElseGet()) для подачі за замовчуванням, або orElseThrow()для викидання вибраного Виняток документує характер проблеми , що краще, ніж загальний NPE, який є безглуздим, поки ви не вкажете в слід стека.
Жуль

0

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

Кращим стилем було б, якби службовецьServive.getE Employee () повертає необов'язково, і тоді код стане:

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

Таким чином, ви не можете calle EmployeeServive.getE Employee (). GetId (), і ви помічаєте, що вам слід якось обробити необов'язкове значення. І якщо у всій вашій кодовій базі методи ніколи не повертатимуть null (або майже ніколи з добре відомими вашій команді винятками з цього правила), це підвищить безпеку проти NPE.


12
Необов'язково стає безглуздим ускладненням в момент використання isPresent(). Ми використовуємо необов’язково, тому нам не потрібно тестувати.
candied_orange

2
Як казали інші, не користуйтеся isPresent(). Це неправильний підхід до факультативів. Використовуйте mapабо flatMapзамість цього.
Андрес Ф.

1
@CandiedOrange І все-таки голосовий відповідь (кажучи використовувати ifPresent) - це ще один спосіб перевірити.
іммібіс

1
@CandiedOrange ifPresent(x)- це так само, як і тест if(present) {x}. Повернене значення не має значення.
іммібіс

1
@CandiedOrange Тим не менш, ви спочатку сказали, "тому нам не потрібно тестувати". Ви не можете сказати "так що нам не доведеться тестувати", коли ви насправді все ще тестуєте ... який ви є.
Аарон

0

Я думаю, що найбільша перевага полягає в тому, що якщо ваш підпис методу повертається, Employeeви не перевіряєте на null. Ви за цим підписом знаєте, що гарантовано повернути працівника. Вам не потрібно займатися справою несправності. З необов'язковим ви знаєте, що ви робите.

У коді, який я бачив, є нульові перевірки, які ніколи не вийдуть з ладу, оскільки люди не хочуть простежити за кодом, щоб визначити, чи можливий нуль, тому вони кидають нульові чеки скрізь оборонно. Це робить код трохи повільнішим, але що важливіше, він робить код набагато шумнішим.

Однак, щоб це працювало, вам потрібно застосовувати цю схему послідовно.


1
Найпростіший спосіб досягти цього - використання @Nullableта @ReturnValuesAreNonnullByDefaultразом зі статичним аналізом.
maaartinus

@maaartinus Додавання додаткового інструментарію у вигляді статичного аналізу та документації в анотаціях декоратора, простіше, ніж тоді компілювати підтримку API часу?
Санки

Додавання додаткового інструменту дійсно є простішим, оскільки воно не потребує змін коду. Більше того, ви хочете, щоб це все одно було, оскільки воно також ловить інших помилок. +++ Я написав тривіальний сценарій , додавши package-info.javaз @ReturnValuesAreNonnullByDefaultусіма моїми пакетами, так що все , що мені потрібно зробити , це додати , @Nullableде ви повинні переключитися на Optional. Це означає менше знаків, без додавання сміття та без накладних витрат. Мені не потрібно шукати документацію, як це видно при наведенні курсору на метод. Інструмент статичного аналізу виявляє помилки та згодом зміни нульових змін.
maaartinus

Моє найбільше занепокоєння Optionalполягає в тому, що він додає альтернативний спосіб поводження з непридатністю. Якщо він був на Яві з самого початку, це може бути добре, але зараз це просто біль. Шлях по Котліну був би ІМХО набагато кращим. +++ Зауважте, що 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));

добре відомі з інших мов. Чи згодні ми з цим твердженням

  • не є (справді) функціональним?
  • дуже схожий на код старого стилю, просто набагато простіше?
  • набагато легше читати, ніж функціональний стиль?
  • є налагоджувачем?

Здається, ви описуєте реалізацію цієї концепції в C # за допомогою мовної функції, яка абсолютно відрізняється від реалізації Java з основним класом бібліотеки. Мені вони так само виглядають
Калет

@Caleth Ні в якому разі. Ми могли б говорити про "концепцію спрощення нульової безпеки", але я б не називав це "концепцією". Можна сказати, що Java реалізує те саме, використовуючи основний клас бібліотеки, але це просто безлад. Почніть з employeeService.getEmployee().getId().apply(id => System.out.println(id));та зробіть це безпечним для нуля один раз, використовуючи оператор безпечної навігації та один раз користуючись Optional. Звичайно, ми могли б назвати це "деталізацією щодо впровадження", але впровадження має значення. Бувають випадки, коли ламда робить код простішим і приємнішим, але тут це просто потворне зловживання.
maaartinus

Бібліотека класів: Optional.of(employeeService).map(EmployeeService::getEmployee).map(Employee::getId).ifPresent(System.out::println);функція Мова: employeeService.getEmployee()?.getId()?.apply(id => System.out.println(id));. Два синтаксиси, але вони зрештою виглядають дуже схоже на мене. А «концепція» OptionalNullableMaybe
відома

Я не розумію, чому ви вважаєте, що одне з них - це "потворне зловживання", а інше - добре. Я міг зрозуміти, що ти думаєш як про "потворне зловживання", так і про добре.
Калет

@Caleth Одна відмінність полягає в тому, що замість того, щоб додавати два знаки запитання, потрібно все це переписати. Проблема не в тому, що особливості мови та класи класів бібліотеки сильно відрізняються. Це добре. Проблема полягає в тому, що вихідний код та коди класів Бібліотеки сильно відрізняються. Та сама ідея, інший код. Це дуже погано. +++Що зловживає, це ламда для поводження з непридатністю. Ламди чудово, але Optionalце не так. Зменшення просто потребує належної підтримки мови, як у Котліна. Ще одна проблема: List<Optional<String>>не пов'язана з тим List<String>, де нам потрібно, щоб вони були пов’язані.
maaartinus

0

Необов’язково - це концепція (тип вищого порядку), що походить від функціонального програмування. Використання його усуває вкладені нульові перевірки і рятує вас від піраміди приреченості.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.