Чи повинні повернути Java 8 отримувачам необов'язковий тип?


288

Optional Тип, представлений у Java 8, є новою річчю для багатьох розробників.

Чи є гарною практикою метод повернення геттерного Optional<Foo>типу замість класичного Foo? Припустимо, що значення може бути null.


8
Хоча це, ймовірно, приверне впевнені відповіді, це гарне питання. Я з нетерпінням чекаю відповіді з реальними фактами з цього приводу.
Джастін

8
Питання в тому, чи неминучість неминуча. Компонент може мати властивість, дозволену нулю, але все ж програміст, який використовує цей компонент, може вирішити суворо зберігати це властивість не null. Тож програмісту тоді не доводилося мати справу Optional. Або, інакше кажучи, nullнасправді відображає відсутність такого значення, як у результаті пошуку (де Optionalце доречно), або є nullлише одним членом набору можливих значень.
Холгер

1
Дивіться також обговорення @NotNullанотацій: stackoverflow.com/q/4963300/873282
koppor

Відповіді:


516

Звичайно, люди будуть робити те, що хочуть. Але ми мали чіткий намір при додаванні цієї функції, і це було не загальним призначенням. Можливо, введіть, як багато людей хотіли б, щоб ми це зробили. Нашим наміром було створити обмежений механізм для типів повернення бібліотечних методів там, де потрібен чіткий спосіб представити "відсутність результату", а використання nullтаких можливостей може призвести до помилок.

Наприклад, ви, ймовірно, ніколи не повинні використовувати його для чогось, що повертає масив результатів або список результатів; замість цього повертайте порожній масив або список. Ви майже ніколи не повинні використовувати його як поле чогось чи параметру методу.

Я думаю, що звичайно використовувати його як повернене значення для одержувачів, безумовно, буде надмірне використання.

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

(Оголошення громадської служби: НІКОЛИ не дзвоніть, Optional.getякщо ви не докажете, що це ніколи не буде нульовим; замість цього використовуйте один із безпечних методів, як orElseабо ifPresent. Заднім числом ми повинні були назвати getщось на кшталт getOrElseThrowNoSuchElementExceptionабо щось, що зробило набагато зрозумілішим, що це дуже небезпечний метод що Optionalв першу чергу підірвало цілі цілі . Урок засвоєний. (ОНОВЛЕННЯ: Java 10 має Optional.orElseThrow(), що семантично еквівалентно get(), але ім'я є більш підходящим.))


24
(Що стосується останньої частини)… і коли ми впевнені, що цінністю nullми ніколи не можемо скористатись orElseThrow(AssertionError::new), ах або orElseThrow(NullPointerException::new)
Holger

25
Що б ви зробили по- іншому , якщо ваш намір було в тому , щоб ввести загальне призначення Може чи інший тип? Чи існує спосіб, за яким Факультативний не відповідає законопроекту для одного, чи це просто введення необов'язкових у всьому новому API, зробить його не-Java-ish?
Девід Молес

22
Під "типом загального призначення" я мав на увазі вбудовувати його в систему типів мови, а не надавати клас бібліотеки, який наближає його. (У деяких мовах є типи для T? (T або null) і T! (Non nullable T)) Необов'язково - це просто клас; ми не можемо робити неявні перетворення між Foo та необов'язковим <Foo>, як ми могли мати мовну підтримку.
Брайан Гец

11
Я деякий час замислювався над тим, чому акцент у світі Java робився на Необов’язковий, а не кращий статичний аналіз. Необов'язково має деякі переваги, але величезною перевагою nullє зворотна сумісність; Map::getповертає нульовий V, а не an Optional<V>, і це ніколи не зміниться. Хоча це можна було б легко помітити @Nullable. Зараз у нас є два способи висловити
нецінність

21
Я поняття не мав, що використовувати його як повернене значення для властивості - це не те, що ви мали намір, поки я не прочитав цю відповідь тут на StackOverflow. Насправді мене припустили, що це саме те, що ви задумали, прочитавши цю статтю на веб-сайті Oracle: oracle.com/technetwork/articles/java/… Дякую за уточнення
Джейсон Томпсон,

73

Провівши трохи власних досліджень, я натрапив на ряд речей, які можуть підказати, коли це доречно. Найавторитетнішою є наступна цитата зі статті Oracle:

"Важливо зауважити, що націляючий клас необов'язковий - не замінювати кожну нульову посилання . Натомість його мета - допомогти розробити зрозуміліші API, щоб, прочитавши підпис методу, можна сказати, чи може очікувати необов'язкове значення. Це змушує вас активно розгортати Факультативні, щоб боротися з відсутністю значення. " - Набридло винятків з нульових покажчиків? Розгляньте можливість використання Java SE 8 додатково!

Я також знайшов цей уривок з Java 8 Необов’язково: Як ним користуватися

"Необов'язково в цих контекстах не використовуватись, оскільки він нічого не купує у нас:

  • в шарі моделі домену (не серіалізується)
  • у DTO (з тієї ж причини)
  • у вхідних параметрах методів
  • в параметрах конструктора "

Що також, схоже, піднімає деякі дійсні моменти.

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


11
stackoverflow.com/questions/25693309 - схоже, Джексон це вже підтримує, тому "не серіалізаційний" більше не проходить як
поважна

1
Я пропоную мати свою відповідь, чому використання Optionalвхідних параметрів методів (точніше конструкторів) "нічого не купить нам". dolszewski.com/java/java-8-optional-use-cases містить приємне пояснення.
Гілі

Я виявив, що для вибору необов'язкових результатів, які потрібно трансформувати в інші API, потрібен необов'язковий параметр. Результат - досить зрозумілий API. Дивіться stackoverflow.com/a/31923105/105870
Карл Язичник

1
Посилання розірвано. Не могли б ви оновити відповідь?
softarn

2
Проблема в тому, що він часто не покращує API, незважаючи на найкращі наміри розробника. Я збирав приклади поганих звичаїв за бажанням , які взяли з виробничого коду, який ви можете перевірити.
МігельМуноз

20

Я б сказав, загалом, гарна ідея використовувати необов'язковий тип для повернення значень, які можуть бути зведені нанівець. Однак, wrt до фреймворків, я вважаю, що заміна класичних геттерів на необов'язкові типи спричинить багато проблем при роботі з фреймворками (наприклад, в сплячому режимі), які покладаються на умови кодування для геттерів та сеттерів.


14
Ця порада - це саме те, що я мав на увазі під назвою "ми стурбовані ризиком ревного надмірного використання" в stackoverflow.com/a/26328555/3553087 .
Брайан Гец

13

Причина 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;

Моя думка полягає в тому, що необов’язково було написано для підтримки функціонального програмування , яке було додано до Java одночасно. (Приклад наданий за допомогою блогу Брайана Гетца . Кращим прикладом може бути orElse()метод, оскільки цей код все одно кине виняток, але ви отримаєте малюнок.)

Але зараз люди користуються Факультативом із зовсім іншої причини. Вони використовують це для усунення недоліків у мовній конструкції. Недолік у цьому: Немає можливості визначити, які з параметрів API та повернених значень дозволені як недійсні. Це може бути зазначено в javadocs, але більшість розробників навіть не пишуть javadocs для свого коду, і не багато хто перевірятиме javadocs, як вони пишуть. Таким чином, це призводить до великої кількості коду, який завжди перевіряє нульові значення, перш ніж використовувати їх, навіть незважаючи на те, що вони часто не можуть бути нульовими, оскільки вони вже перевірені дев'ять або десять разів вище стека викликів.

Я думаю, що була вирішена ця вада, тому що так багато людей, які бачили новий клас необов’язків, припускали, що його мета - додати ясність API. Ось чому люди задають питання на кшталт "чи повинні повернути необов’язкові користувачі?" Ні, вони, мабуть, не повинні, якщо ви не очікуєте, що геттер буде використаний у функціональному програмуванні, що дуже малоймовірно. Насправді, якщо поглянути на те, де додатково використовується Java API, то це переважно в класах Stream, які є ядром функціонального програмування. (Я не перевірив дуже ретельно, але класи Stream можуть бути єдиним місцем, де вони використовуються.)

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

О, і якщо вам потрібно, щоб ваш клас був серіалізаційним, ви абсолютно не повинні користуватися факультативно.

Необов’язково - це дуже погане рішення вади API, оскільки: а) вони дуже багатослівні; б) вони ніколи не мали наміру вирішити цю проблему в першу чергу.

Набагато кращим рішенням вади API є перевірка нульовості . Це процесор анотацій, який дає змогу вказати, які параметри та значення повернення дозволені до нуля, анотувавши їх за допомогою @Nullable. Таким чином, компілятор може сканувати код і з'ясувати, чи передається значення, яке насправді є нульовим, до значення, де null заборонено. За замовчуванням він передбачає, що нічого не можна вважати нульовим, якщо це так не зазначається. Таким чином, вам не доведеться турбуватися про нульові значення. Передача нульового значення параметру призведе до помилки компілятора. Тестування об'єкта на null, який не може бути null, створює попередження компілятора. Ефект цього полягає в зміні NullPointerException з помилки виконання на помилку часу компіляції.

Це все змінює.

Що стосується ваших людей, не використовуйте необов’язково. І спробуйте спроектувати свої класи, щоб жоден з членів не міг бути нульовим. І, можливо, спробуйте додати у свій проект Checker Nullness і оголосити ваші геттери та параметри setter @Nullable, якщо вони цього потребують. Я це робив лише з новими проектами. Це, мабуть, створює багато попереджень у існуючих проектах, написаних з безліччю зайвих тестів на нуль, тому може бути важко доопрацювати. Але це також зловить багато клопів. Я це люблю. Мій код набагато чистіший і надійніший через нього.

(Існує також нова мова, яка стосується цього. Kotlin, який компілює в байтний код Java, дозволяє вам вказати, чи може об'єкт бути недійсним, коли ви декларуєте його. Це більш чистий підхід.)

Додаток до оригінальної публікації (версія 2)

Задумавшись, я неохоче прийшов до висновку, що повернути Факультативно прийнятно за однієї умови: щоб отримане значення насправді було недійсним. Я бачив багато коду, коли люди звичайно повертають необов’язково від одержувачів, які не можуть повернути нуль. Я вважаю це дуже поганою практикою кодування, яка лише додає складності коду, що робить помилки більш імовірними. Але коли повернене значення може бути фактично недійсним, продовжуйте і загортайте його в необов’язковий.

Майте на увазі, що методи, розроблені для функціонального програмування та потребують посилання на функцію, будуть (і повинні) бути написані у двох формах, в одній з яких використовується Необов'язково. Наприклад, Optional.map()і Optional.flatMap()обидва беруть посилання на функції. Перший посилається на звичайний геттер, а другий - той, що повертає необов’язково. Таким чином, ви не робите нікому прихильності, повертаючи необов’язково, коли значення не може бути нульовим.

Сказавши все це, я все ще бачу підхід, який застосовує перевірка нульовості, є найкращим способом боротьби з нулями, оскільки вони перетворюють NullPointerExceptions з помилок виконання для компіляції помилок часу.


2
Ця відповідь здається мені найбільш прийнятною. Необов’язково додано лише у java 8, коли додано потоки. І лише потокові функції повертають необов’язково, наскільки я бачив.
Архит

1
Я думаю, що це одна з найкращих відповідей. Люди знають, що є необов’язковим і як це працює. Але найбільш заплутаною частиною є / було де його використовувати і як ним користуватися. прочитавши це, воно знімає масу сумнівів.
ParagFlume

3

Якщо ви використовуєте сучасні серіалізатори та інші рамки, які розуміють, Optionalя виявив, що ці вказівки добре працюють під час написання Entityбобів та шарів домену:

  1. Якщо рівень серіалізації (як правило, БД) дозволяє отримати nullзначення для комірки в стовпці BARтаблиці FOO, то геттер Foo.getBar()може повернути Optionalвказівку розробнику, що, можливо, очікується, що це значення буде нульовим, і вони повинні це обробити. Якщо БД гарантує, що значення не буде нульовим, то отримувач не повинен обертати це в Optional.
  2. Foo.barповинно бути privateі не бути Optional. Насправді немає причин для цього, Optionalякщо воно є private.
  3. Сеттер Foo.setBar(String bar) повинен приймати тип, barа не Optional . Якщо добре, використовуйте nullаргумент, тоді вкажіть це в коментарі JavaDoc. Якщо це не нормально використовувати nullв IllegalArgumentExceptionабо відповідну бізнес - логіку це, імхо, більш доречно.
  4. Конструктори не потребують Optionalаргументів (з причин, подібних до пункту 3). Як правило, я включаю в конструктор лише аргументи, які повинні бути ненулевими в базі даних серіалізації.

Щоб зробити вищезазначене більш ефективним, ви можете відредагувати шаблони IDE для створення гетерів та відповідних шаблонів для toString(),equals(Obj o) і т.д. , або полів використовують безпосередньо для тих , хто (більшість IDE генераторів вже мати справу з нулях).

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