Вопрос-ответ

Uses for Optional

Использует для необязательных

Я использую Java 8 уже более 6 месяцев или около того, и я вполне доволен новыми изменениями API. Одна область, в которой я все еще не уверен, - это когда использовать Optional. Кажется, я колеблюсь между желанием использовать его везде, где что-то может быть null, и вообще нигде.

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

Итак, у меня есть несколько примеров, и мне было бы интересно узнать мнение сообщества о том, Optional полезно ли это.

1 - В качестве возвращаемого типа открытого метода, когда метод может возвращать null:

public Optional<Foo> findFoo(String id);

2 - В качестве параметра метода, когда параметр может быть null:

public Foo doSomething(String id, Optional<Bar> barOptional);

3 - В качестве необязательного члена компонента:

public class Book {

private List<Pages> pages;
private Optional<Index> index;

}

4 - В Collections:

В общем, я не думаю, что:

List<Optional<Foo>>

добавляет что угодно - особенно потому, что можно использовать filter() для удаления null значений и т.д., Но есть ли какие-либо полезные применения для Optional в коллекциях?

Какие-нибудь случаи, которые я пропустил?

Переведено автоматически
Ответ 1

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

Это наиболее точно соответствует варианту использования # 1 в вопросе OP. Хотя, отсутствие значения является более точной формулировкой, чем null, поскольку что-то вроде IntStream.findFirst никогда не сможет вернуть null.


В случае использования # 2, передачи необязательного аргумента методу, это можно было бы заставить работать, но это довольно неуклюже. Предположим, у вас есть метод, который принимает строку, за которой следует необязательная вторая строка. Принятие Optional в качестве второго аргумента приведет к коду, подобному этому:

foo("bar", Optional.of("baz"));
foo("bar", Optional.empty());

Даже принимать null приятнее:

foo("bar", "baz");
foo("bar", null);

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

foo("bar", "baz");
foo("bar");

У этого есть ограничения, но это намного приятнее, чем любое из вышеперечисленных.

Варианты использования #3 и #4, имеющие значение Optional в поле класса или в структуре данных, считаются неправильным использованием API. Во-первых, это противоречит основной цели разработки Optional, как указано вверху. Во-вторых, это не добавляет никакой ценности.

Есть три способа справиться с отсутствием значения в Optional: предоставить заменяющее значение, вызвать функцию для предоставления заменяющего значения или создать исключение. Если вы сохраняете данные в поле, вы должны делать это во время инициализации или назначения. Если вы добавляете значения в список, как упоминалось в OP, у вас есть дополнительный выбор - просто не добавлять значение, тем самым "выравнивая" отсутствующие значения.

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

Ответ 2

Я опаздываю на игру, но, как бы то ни было, я хочу добавить свои 2 цента. Они идут вразрез с целью разработки Optional, которая хорошо обобщена в ответе Стюарта Маркса, но я все еще убежден в их обоснованности (очевидно).

Используйте необязательное везде

В общих чертах

Я написал целый пост в блоге об использовании Optional, но в основном это сводится к следующему:


  • создавайте свои классы таким образом, чтобы избегать необязательности везде, где это возможно

  • во всех остальных случаях по умолчанию должно использоваться Optional вместо null

  • возможно создание исключений для:

    • локальные переменные

    • возвращает значения и аргументы частным методам

    • блоки кода, критичные к производительности (никаких предположений, используйте профилировщик)



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

Обратите внимание, что это почти никогда не допускает Optionals в коллекциях, что почти так же плохо, как nulls. Просто не делайте этого. ;)

Что касается ваших вопросов


  1. ДА.

  2. Если перегрузка не является опцией, да.

  3. Если другие подходы (создание подклассов, декорирование, ...) не подходят, да.

  4. Пожалуйста, нет!

Преимущества

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

Разъясняет намерение

Использование Optional четко выражает, что переменная, ну, необязательна. Любой читатель вашего кода или потребитель вашего API будет ошарашен тем фактом, что там может ничего не быть и что перед доступом к значению необходима проверка.

Устраняет неопределенность

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

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

Дополнительные проверки Null

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

Недостатки

Конечно, здесь нет серебряной пули...

Производительность

Перенос значений (особенно примитивов) в дополнительный экземпляр может снизить производительность. В жестких циклах это может стать заметным или даже хуже.

Обратите внимание, что компилятор может быть в состоянии обойти дополнительную ссылку из-за короткого времени жизни Optionals. В Java 10 типы значений могут дополнительно уменьшить или убрать штраф.

Сериализация

Optional не сериализуем, но обходной путь не слишком сложен.

Инвариантность

Из-за неизменности универсальных типов в Java некоторые операции становятся громоздкими, когда фактический тип значения помещается в аргумент универсального типа. Пример приведен здесь (см. "Параметрический полиморфизм").

Ответ 3

Лично я предпочитаю использовать инструмент проверки кода IntelliJ для проверки @NotNull и @Nullable, поскольку они в основном выполняются во время компиляции (могут иметь некоторые проверки во время выполнения) Это снижает накладные расходы с точки зрения удобочитаемости кода и производительности во время выполнения. Это не так строго, как использование Optional, однако это отсутствие строгости должно быть подкреплено достойными модульными тестами.

public @Nullable Foo findFoo(@NotNull String id);

public @NotNull Foo doSomething(@NotNull String id, @Nullable Bar barOptional);

public class Book {

private List<Pages> pages;
private @Nullable Index index;

}

List<@Nullable Foo> list = ..

Это работает с Java 5, и нет необходимости переносить и разворачивать значения. (или создавать объекты-оболочки)

Ответ 4

Я думаю, что Guava Optional и их вики-страница описывают это довольно хорошо:


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


Это особенно актуально, когда вы возвращаете значения, которые могут "присутствовать", а могут и не присутствовать. Вы (и другие) с гораздо большей вероятностью забудете, что other.method(a, b) может возвращать значение null, чем вы, вероятно, забудете, что a может быть null при реализации other.method. Возврат значения Optional не позволяет вызывающим пользователям забыть этот случай, поскольку им приходится самим разворачивать объект для компиляции своего кода. -- (Источник: Guava Wiki - Использование и избежание null - В чем смысл?)


Optional добавляет некоторые накладные расходы, но я думаю, что его явное преимущество заключается в том, чтобы сделать явным, что объект может отсутствовать, и это заставляет программистов справиться с ситуацией. Это предотвращает то, что кто-то забудет свою любимую != null проверку.

На примере 2, я думаю, что это гораздо более явный код для написания:

if(soundcard.isPresent()){
System.out.println(soundcard.get());
}

чем

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

Для меня Optional лучше отражает тот факт, что звуковой карты нет.

Мои 2¢ о ваших замечаниях:


  1. public Optional<Foo> findFoo(String id); - Я не уверен в этом. Возможно, я бы вернул a, Result<Foo> который может быть пустым или содержать Foo. Это похожая концепция, но не совсем Optional.

  2. public Foo doSomething(String id, Optional<Bar> barOptional); - Я бы предпочел @Nullable и проверку findbugs, как в ответе Питера Лоури - смотрите Также Это обсуждение.

  3. Пример из вашей книги - я не уверен, буду ли я использовать Optional внутри, это может зависеть от сложности. Для "API" книги я бы использовал Optional<Index> getIndex(), чтобы явно указать, что у книги может не быть индекса.

  4. Я бы не использовал его в коллекциях, а не допускал бы нулевые значения в коллекциях

В общем, я бы постарался свести к минимуму передачу nulls. (После записи ...) Я думаю, стоит найти соответствующие абстракции и указать коллегам-программистам, что на самом деле представляет определенное возвращаемое значение.

java java-8