Що таке Optional?
Параметр Optional використовується для перенесення об'єктів та забезпечення обробки посилань на null за допомогою різних API-інтерфейсів. Давайте подивимося на уривок коду:Coffee coffee = new Coffee();
Integer quantity = coffee.getSugar().getQuantity();
У нас є екземпляр Coffee , в якому ми отримуємо деяку кількість sugar з екземпляра об'єкта Sugar . Якщо ми припустимо, що значення кількості ніколи не встановлювалося в конструкторі Coffee , то coffee.getSugar().getQuantity() поверне виняток NullPointerException . Звичайно, ми завжди можемо використовувати старі добрі перевірки null , щоб виправити проблему.
Coffee coffee = new Coffee();
Integer quantity = 0;
if (coffee.getSugar() != null) {
quantity = coffee.getSugar().getQuantity();
}
Тепер начебто все нормально. Але при написанні Java-коду нам краще уникати реалізації перевірок null . Погляньмо, як це можна зробити, використовуючи Optional.
Як створити Optional
Існує три способи створення об'єктів Optional:-
of(T value) - створення екземпляра Optional ненульового (non-null) об'єкта. Майте на увазі, що використання of() посилання на об'єкт null призведе до появи NullPointerException .
-
ofNullable(T value) - Створення значення Optional для об'єкта, який може бути нульовим (null).
-
empty() - створення екземпляра Optional, який представляє посилання на null .
// пример использования Optional.of(T Value)
String name = "foo";
Optional<String> stringExample = Optional.of(name)
// пример использования Optional.ofNullable(T Value)
Integer age = null;
Optional<Integer> integerExample= Optional.ofNullable(age)
// пример использования Optional.empty()
Optional<Object> emptyExample = Optional.empty();
Отже, у вас є об'єкт Optional. А тепер давайте познайомимося із двома основними методами для Optional:
-
isPresent() — цей метод повідомляє вам, чи містить об'єкт Optional ненульове (non-null) значення.
-
get() — витягує значення для Optional з поточним значенням. Майте на увазі, що виклик get() для порожнього Optional призведе до NullPointerException .
Поліпшення перевірки Null за допомогою Optional
Отже, як нам покращити наведений вище код? З Optional можна зрозуміти наявність об'єкта за допомогою isPresent() і отримати його за допомогою get() . Почнемо з упаковки результату coffee.getSugar() з Optional та використанням методу isPresent() . Це допоможе нам визначити, чи getSugar() повертає значення null.Coffee coffee = new Coffee();
Optional<String> sugar = Optional.ofNullable(coffee.getSugar());
int quantity = 0;
if (sugar.isPresent()) {
Sugar sugar = sugar.get();
int quantity = sugar.getQuantity();
}
Дивлячись на цей приклад, упаковка результату coffee.getSugar() в Optional, схоже, не додає жодної цінності, а скоріше додає клопоту. Ми можемо покращити результат, використовуючи те, що я вважаю своїми улюбленими функціями з класу Optional:
-
map(Function<? super T,? extends U> mapper) - перетворює значення, що міститься в Optional, з наданою функцією. Якщо параметр Optional порожній, то map() поверне Optional.empty() .
-
orElse(T other) - це "спеціальна" версія методу get() . Вона може отримати значення, що міститься в Optional. Однак у разі порожнього Optional це поверне значення, передане в метод orElse() .
Coffee coffee = new Coffee();
Integer quantity = Optional.ofNullable(coffee.getSugar())
.map(it -> it.getQuantity())
.orElse(0);
Це реально круто – принаймні я так думаю. Тепер, якщо у разі порожнього значення ми не хочемо повертати значення за замовчуванням, нам потрібно створити якийсь виняток. orElseThrow(Supplier<? extends X> exceptionSupplier) повертає значення, що міститься в параметрах Optional, або видає виняток у разі порожнього Optional.
Coffee coffee = new Coffee();
Integer quantity = Optional.ofNullable(coffee.getSugar())
.map(it -> it.getQuantity())
.orElseThrow(IllegalArgumentException::new);
Як бачите, Optional дає кілька переваг:
- абстрагує перевірки null
- надає API для обробки об'єктів null
- дозволяє декларативному підходу висловлювати те, що досягається
Як стати ефективним з Optional
У своїй роботі я використовую Optional як тип, що повертається, коли метод може повертати стан “no result”. Зазвичай я використовую його при визначенні типів, що повертаються для методів.Optional<Coffee> findByName(String name) {
...
}
Іноді в цьому немає потреби. Наприклад, якщо у мене є метод, що повертає int , наприклад getQuantity() в класі Sugar , тоді метод може повернути 0 якщо результат дорівнює null , щоб представити "no quantity" (немає кількості). Тепер, знаючи це, ми можемо подумати, що параметр Sugar у класі Coffee може бути представлений як Optional. На перший погляд це здається гарною ідеєю, оскільки теоретично цукор не обов'язково має бути присутнім у каві. Однак саме тут я хотів би зупинитися на тому, коли не слід використовувати Optional. Ми повинні уникати використання Optional у таких сценаріях:
-
Як типи параметрів для POJO , таких як об'єкти DTO . Optional не серіалізується, тому їх використання в POJO позбавляє об'єкт можливості серіалізації.
-
Як аргумент методу. Якщо аргумент методу може бути null , то з точки зору чистого коду передача null, як і раніше, краще, ніж передача Optional. Крім того, ви можете створити перевантажені методи абстрактної обробки відсутності аргументу нульового методу.
-
Для представлення об'єкта Collection, який немає. Колекції можуть бути порожніми, тому порожня Collection , як порожній Set або List , повинна використовуватися для подання Collection без значень.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ