- Что такое Optional, почему он полезен? ->
- Как использовать Optional ->
- Использование, уместное и не очень ->
- Холивары ->
- Итоги ->
Что такое Optional, почему он полезен?
Optional<T>
был введен в Java 8- Может находиться в 2ух состояниях
- Содержит ссылку на T (также «present» или присутствует)
- Пуст (также «absent» или отсутствует. Не употребляйте «null»)
- Реализации для примитивных типов
- OptionalInt, OptionalLong, OptionalDoube
- Optional — ссылочный тип, он может быть null (! Никогда так не делайте)
Правило #1: Никогда не используйте null как значение Optional или в качестве возвращаемого значения
// Поиск клиента в списке List<Customer> по заданному ид
// Early draft API: Stream.search(Predicate)
Customer customerByID(List<Customer> custList, int custID) {
return custList.stream()
.search(c -‐> c.getID() == custID);
}
// Что будет, если в List нет элемента с custID?
// Предположительно search() вернет null
// customerByID() вернет null
Если нам нужно получить не клиента, а его имя?
// Возвращаем имя клиента
Customer customerByID(List<Customer> custList, int custID) {
return custList.stream()
.search(c -‐> c.getID() == custID)
.getName(); /// Получим NullPointerException если не найдем Customer
}
// Возвращаем имя клиента
Customer customerByID(List<Customer> custList, int custID) {
Customer cust =custList.stream()
.search(c -‐> c.getID() == custID)
return cust != null ? cust.getName() : "Не найдено"; // А вот это очень легко забыть!
}
Итак:
Optional — это ограничительный механизм для методов возвращаемые значения которых требуют отдавать некоторый «no result», представленный null, который может вызвать дальнейшие ошибки
Как использовать Optional
Рассмотрим пример с использованием Optional
// Будем использовать настоящие методы Streams API: findFirst() и findAny()
String customerNameByID(List<Customer> custList, int custID) {
return custList.stream()
.filter(c ‐> c.getID() == custID)
.findFirst() // Вернет Optional
.getName(); // Так будет ошибка. Так как нужно как-то из Optional достать Customer
}
// Можно попробовать сделать так
opt.get().getName(); // Рискуем получить NoSuchElementException, если Optional пуст
// Хм, давайте просто добавим проверку!
opt.isPresent() ? opt.get().getName() : "Не найдено"; // Безопасно, но не сильно лучше чем проверка на null в лоб :)
Правило #2: Никогда не используйте Optional.get() без предварительной проверки Optional.isPresent()
Правило #3: Выбирайте отличные способы работы с Optional, чем проверка на isPresent + get()
Как же тогда быть?
// .orElse()
// orElse(default)
Optional<Data> opt = ...
Data data = opt.orElse(DEFAULT_DATA); // Если пуст, вернет указанное значение
// orElseGet(supplier)
Optional<Data> opt = ...
Data data = opt.orElseGet(Data::new); // Если пуст, вернет результат вызова метода new
// orElseThrow(exsupplier)
Optional<Data> opt = ...
Data data = opt.orElseThrow(IllegalStateException::new); // Если пуст, бросит ошибку
// .map()
opt.map(Customer::getName).orElse("Не найдено"); // map вызовет метод getName на объекте, если opt не пуст, иначе вернет пустой Optional
// не путать с isPresent()!
// Плохо с if
Optional<Task> oTask = getTask(...);
if (oTask.isPresent()) {
executor.runTask(oTask.get());
}
// Уже лучше:
getTask(...).ifPresent(task ‐> executor.runTask(task));
// Идеально!
getTask(...).ifPresent(executor::runTask);
Дополнительные методы
- Статичные фабричные
- Optional.empty() — создает пустой Optional
- Optional.of(T) — создает Optional T (T не может быть null)
- flatMap(Function<T, Optional<U>>)
- Работает как map(), но использует для трансформации функцию, возвращающую Optional
- Optional.equals() и hashCode()
// Конвертируем List<CustomerID> в List<Customer>, игнорируя неизвестные
// Пусть .findByID() возвращает Optional<Customer>
// Java 8
List<Customer> list = custIDlist.stream()
.map(Customer::findByID)
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toList());
// Java 9 Фича Optional.stream(), позволяет использовать в flatMap
List<Customer> list = custIDlist.stream()
.map(Customer::findByID)
.flatMap(Optional::stream)
.collect(Collectors.toList());
Адаптер null-optional
- Есть nullable значение, нужен Optional
- Optional<T> opt = Optional.ofNullable(ref);
- Есть Optional, нужен nullable
- opt.orElse(null); ! В других случаях избегайте этого
Использование, уместное и не очень.
Рассмотрим на примерах
// Плохая идея
String process(String s) {
return Optional.ofNullable(s).orElseGet(this::getDefault);
}
// А вот так лучше
String process(String s) {
return (s != null) ? s : getDefault();
}
Правило #4: В целом, создавать Optional только для того что бы получить из него значения — плохая идея. Лучше использовать тренарный оператор «?»
Optional<BigDecimal> first = getFirstValue();
Optional<BigDecimal> second = getSecondValue();
// Сложить first и second, рассматривая пустой как ZERO
// Вернуть Optional суммы
// Если оба пусты, вернуть пустой Optional
// Заумно
Optional<BigDecimal> result =
Stream.of(first, second)
.filter(Optional::isPresent)
.map(Optional::get)
.reduce(BigDecimal::add);
// Ммм. Еще заумней... Попробуйте проверить корректно ли это?
Optional<BigDecimal> result =
first.map(b ‐> second.map(b::add).orElse(b))
.map(Optional::of)
.orElse(second);
// Не самый короткий и искусный вариант, но самый понятный
Optional<BigDecimal> result;
if (!first.isPresent() && !second.isPresent()) {
result = Optional.empty();
} else {
result = Optional.of(first.orElse(ZERO).add(second.orElse(ZERO)));
}
Правило #5: Если есть вложенная цепочка Optional или промежуточный результат Optional<Optional<T>>, вероятно, это излишне
Этот проблемный метод get()
В интернете существует много обсуждений и много вопросов, связанных с этим методом. Основной момент — он ведет себя не так, как кажется на первый взгляд. Вызывая его, нужно осознавать, если Optional будет пуст, вы получите Exception! Общие рекомендации — не использовать его или делать крайне осторожно.
- Метод get() — «attractive nuisance» или «заманчивая неприятность»
- не сильно полезен
- легко забыть обезопасить вызов
- легко сбивает с толку стиль isPresent() + get()
- неправильно используется в большом количестве случаев => Не совсем удачное API
- Планы по ликвидации проблемы. (Возможно будет уже в Java9?)
- представить замену get()
- @Deprecated для get()
Правило #6: Не используйте Optional в полях объекта, параметрах методов и коллекциях
- Помните, Optional это box!
- требует 16 байт
- это отдельный объект (создает нагрузку на GC)
- Одиночный Optional — не проблема, но если в вашей структуре данных их много, это может привести к проблемам с производительностью
- Не нужно пытаться заменить каждый null на Optional
- null — безопасен, если хорошо контролируется
- null в private поле легко проверяется
- null в параметрах — это не плохо (кончено, если код содержит предпроверки)
Холивары
- Optional должен иметь возможность быть «present» со значением null!
- Optional не защищает от всех NPE, поэтому он бесполезен
- Optional должен быть serializable!
- Optional должен быть частью языка, а не частью вспомогательной библиотеки!
- Optional не должен быть final!
- Optional должен расширять интерфейс Iterable, что позволит использовать его в циклах for!
- У Optional должны быть дочерние классы Present и Empty!
- Optional.ifPresent() должен возвращать «this» вместо void. Что бы использовать его в цепочках!
- Нужно добавить в Java @Nullable / @NonNull вместо Optional!
Итоги
Новые методы Optional в Java9
- Stream<T> Optional.stream()
- void Optional.ifPresentOrElse(Consumer<T>, Runnable)
- Optional<T> Optional.or(Supplier<Optional<T>>)
Правила
- Никогда не используйте null как значение Optional или в качестве возвращаемого значения
- Никогда не используйте Optional.get() без предварительной проверки Optional.isPresent()
- Выбирайте отличные способы работы с Optional, чем проверка на isPresent + get()
- В целом, создавать Optional только для того что бы получить из него значения — плохая идея. Лучше использовать тренарный оператор «?»
- Если есть вложенная цепочка Optional или промежуточный результат Optional<Optional<T>>, вероятно, это излишне
- Не используйте Optional в полях объекта, параметрах методов и коллекциях
Добавлю немного от себя:
Optional, наряду с stream API, и lambda — удобная фича языка, позволяющая улучшить читаемость кода, оптимизировать проверки, избежать не обрабатываемых exception. Главное правило — не забывать, что код нужно писать так, что бы коллега мог легко понять что тут происходит, и открыв его через пару месяцев после написания не пришлось заново разбирать всю логику :)
Код ради кода — не есть это хорошо
С большой силой приходит большая ответственность
Оригинал: Optional – The Mother of all Bikesheds
Alex
07 декабря 2017 — 23:02
Интересная на самом деле статья, т. к. не давно у нас на проекте появилассь поддержка java 8 и все принялись писать код изпользуя Optional, не изучив толком как работает данный класс.
У меня есть пример где применял этот класс в рамках учебной практики по учебнику Head First https://github.com/turbomann/HF/blob/master/src/main/java/trx0eth7/chapter1/BeerSong.java
Сергей Бойченко
08 декабря 2017 — 00:18
Да,он так и манит написать
Optional.ofNullable(someVar).orElse(null)
:)Еще не начали повально циклы на
stream`ы
переписывать?Alex
10 декабря 2017 — 13:19
Так и есть) И это приводит порой к нечитаемому виду. Если полезть то как реализованы stream`ы, то по сути это такой же код, который мы писали до.
А вообще спасибо за статью.