Різниця між `Optional.orElse ()` та `Optional.orElseGet ()`


206

Я намагаюся зрозуміти різницю між методами Optional<T>.orElse()та Optional<T>.orElseGet().

Опис orElse()методу - "Поверніть значення, якщо воно є, інакше поверніть інше".

Тим не менш, опис orElseGet()методу - "Повернути значення, якщо воно є, інакше викликайте інше та поверніть результат цього виклику."

orElseGet()Метод приймає функціональний інтерфейс постачальника, який по суті не приймає ніяких параметрів і повертає T.

У якій ситуації вам потрібно було б скористатися orElseGet()? Якщо у вас є метод, T myDefault()чому б ви не просто зробили, optional.orElse(myDefault())а не optional.orElseGet(() -> myDefault())?

Здається, це не orElseGet()відкладає виконання виступу лямбда на якийсь пізній час чи щось таке, тож у чому сенс? (Я б подумав, що було б корисніше, якби він повернув безпечніший Optional<T>, який get()ніколи не кидає NoSuchElementExceptionі isPresent()завжди повертає справжнє ... але, очевидно, його немає, він просто повертається Tяк orElse()).

Чи є якась інша різниця, яку мені не вистачає?


7
Причина, коли ви користуєтесь, orElseGetвикликає постачальника лише у випадку відсутності значення.
Алекс Салауйо

9
Ах добре це зрозуміло. Таким чином , в разі orElse()з myDefault()методом ще називають, але її значення, що повертається просто не використовується.
jbx

3
Питання, що сприймається, тому що через те, що я бачив непорозуміння чи просто забуття використовувати, orElseGet()може призвести до серйозних помилок: medium.com/alphadev- iakots/…
softarn

Тут є хороше пояснення: baeldung.com/java-optional-or-else-vs-or-else-get
Нестор

Відповіді:


172

Візьміть ці два сценарії:

Optional<Foo> opt = ...
Foo x = opt.orElse( new Foo() );
Foo y = opt.orElseGet( Foo::new );

Якщо optзначення не містить, два дійсно є рівнозначними. Але якщо opt дійсно містить значення, скільки Fooоб'єктів буде створено?

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


22
Дякую за роз'яснення, хлопці. Тож різниця тонка, але значна. У другому випадку він не створить новий Fooоб’єкт, тоді як у першому випадку він створить його, але не використовуватиме його, якщо всередині є значення Optional.
jbx

5
@jbx Так, і в моєму примхливому прикладі це, мабуть, не має жодної реальної різниці, але якщо вам доведеться отримати значення за замовчуванням з віддаленого веб-сервісу, наприклад, або з бази даних, різниця стає раптом дуже важливою.
biziclop

2
@jbx: ви змішуєте дві речі. В СО вже є запитання щодо дивних результатів порівняльних показників, які просто були викликані не використанням результату обчислення. JVM може це зробити. З іншого боку, System.out.println()це не розрахунок, а твердження, що створює помітний побічний ефект. І я вже говорив, що помітні побічні ефекти будуть перешкоджати оптимізації (вихідний потік консолі - це зовнішній ресурс).
Холгер

7
Це перший раз, коли я бачу запитання замість відповіді.
Кирило Г.

4
" якщо вам доведеться отримати значення за замовчуванням через віддалену веб-службу, наприклад ", це був саме мій сценарій. У моєму випадку необов'язковим був запит, а за замовчуванням за відсутності запиту було отримати всі значення ... так, абоElseGet скоротив час виконання цієї операції в 1000 разів.
Скоттісей

109

Коротка відповідь:

  • orElse () завжди буде викликати задану функцію, хочете ви цього чи ні, незалежно від Optional.isPresent()значення
  • orElseGet () буде викликати цю функцію лише тоді, колиOptional.isPresent() == false

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

// Always get heavy resource
getResource(resourceId).orElse(getHeavyResource()); 

// Get heavy resource when required.
getResource(resourceId).orElseGet(() -> getHeavyResource()) 

Детальніше розглянемо наступний приклад з цією функцією:

public Optional<String> findMyPhone(int phoneId)

Різниця полягає в наступному:

                           X : buyNewExpensivePhone() called

+——————————————————————————————————————————————————————————————————+——————————————+
|           Optional.isPresent()                                   | true | false |
+——————————————————————————————————————————————————————————————————+——————————————+
| findMyPhone(int phoneId).orElse(buyNewExpensivePhone())          |   X  |   X   |
+——————————————————————————————————————————————————————————————————+——————————————+
| findMyPhone(int phoneId).orElseGet(() -> buyNewExpensivePhone()) |      |   X   |
+——————————————————————————————————————————————————————————————————+——————————————+

Коли optional.isPresent() == false, різниці між двома способами немає. Однак коли optional.isPresent() == true, orElse()викликуйте наступну функцію, хочете ви цього чи ні.

Нарешті, тестовий зразок використовується нижче:

Результат:

------------- Scenario 1 - orElse() --------------------
  1.1. Optional.isPresent() == true
    Going to a very far store to buy a new expensive phone
    Used phone: MyCheapPhone

  1.2. Optional.isPresent() == false
    Going to a very far store to buy a new expensive phone
    Used phone: NewExpensivePhone

------------- Scenario 2 - orElseGet() --------------------
  2.1. Optional.isPresent() == true
    Used phone: MyCheapPhone

  2.2. Optional.isPresent() == false
    Going to a very far store to buy a new expensive phone
    Used phone: NewExpensivePhone

Код:

public class TestOptional {
    public Optional<String> findMyPhone(int phoneId) {
        return phoneId == 10
                ? Optional.of("MyCheapPhone")
                : Optional.empty();
    }

    public String buyNewExpensivePhone() {
        System.out.println("\tGoing to a very far store to buy a new expensive phone");
        return "NewExpensivePhone";
    }


    public static void main(String[] args) {
        TestOptional test = new TestOptional();
        String phone;
        System.out.println("------------- Scenario 1 - orElse() --------------------");
        System.out.println("  1.1. Optional.isPresent() == true");
        phone = test.findMyPhone(10).orElse(test.buyNewExpensivePhone());
        System.out.println("\tUsed phone: " + phone + "\n");

        System.out.println("  1.2. Optional.isPresent() == false");
        phone = test.findMyPhone(-1).orElse(test.buyNewExpensivePhone());
        System.out.println("\tUsed phone: " + phone + "\n");

        System.out.println("------------- Scenario 2 - orElseGet() --------------------");
        System.out.println("  2.1. Optional.isPresent() == true");
        // Can be written as test::buyNewExpensivePhone
        phone = test.findMyPhone(10).orElseGet(() -> test.buyNewExpensivePhone());
        System.out.println("\tUsed phone: " + phone + "\n");

        System.out.println("  2.2. Optional.isPresent() == false");
        phone = test.findMyPhone(-1).orElseGet(() -> test.buyNewExpensivePhone());
        System.out.println("\tUsed phone: " + phone + "\n");
    }
}

Я думаю, що у вас може виникнути помилка у вашій картинці, вона повинна говорити "orElseGet" праворуч? Крім того, чудовий приклад.
Ялла Т.

Так, ви праві. Дякую :) Я
оновлю

Щодо другої точки кулі, здається, має бути Optional.isPresent() == falseзамість цього (помилково, неправда)
Мануель Йордан

Чудовий приклад - але я дійсно не розумію, як Javadocs, для Optional.orElseяких штати If a value is present, returns the value, otherwise returns otherможуть натякати таку поведінку ...
Ерік Фінман

На підставі ваших пояснень, для мене це виглядає як що orElse()працює аналогічно finallyв try-catchвираженні. Я прав?
Майк Б.

63

Я доїхав сюди до проблеми, про яку згадував Кудо .

Я ділюсь своїм досвідом для інших.

orElseабо orElseGet, ось питання:

static String B() {
    System.out.println("B()...");
    return "B";
}

public static void main(final String... args) {
    System.out.println(Optional.of("A").orElse(B()));
    System.out.println(Optional.of("A").orElseGet(() -> B()));
}

відбитки

B()...
A
A

orElseоцінює значення B () взаємозалежно від значення необов'язкового. Таким чином, orElseGetлінивий.


7
Це не «проблема». Це простий факт, що аргумент для методу оцінюється перед виконанням методу. Якщо ви переходите B()до методу з назвою orElse()або abc()він не має ніяких змін, B()оцінюється.
jbx

11
Проблема тут насправді називання методів. У orпрефіксів дезорієнтує розробників ( в тому числі себе , коли я запитав проблему), думаючи , що це коротке замикання операції, тому що це те , що ми звикли в логічних умовах. Однак це не так, це лише ім'я методу, яке має orсвій префікс, тому його аргументи будуть оцінені, незалежно від того, Optionalмає значення чи ні. Прикро, що називання заплутане, а не що ми можемо з цим щось зробити.
jbx

37

Я б сказав, що найбільша різниця між orElseі orElseGetвиникає, коли ми хочемо щось оцінити, щоб отримати нове значення в elseумові.

Розглянемо цей простий приклад -

// oldValue is String type field that can be NULL
String value;
if (oldValue != null) {
    value = oldValue;
} else {
    value = apicall().value;
}

Тепер давайте перетворимо наведений вище приклад на використання Optionalразом із orElse,

// oldValue is Optional type field
String value = oldValue.orElse(apicall().value);

Тепер давайте перетворимо наведений вище приклад на використання Optionalразом із orElseGet,

// oldValue is Optional type field
String value = oldValue.orElseGet(() -> apicall().value);

Коли orElseвикликається, apicall().valueоцінюється та передається методу. Тоді як у випадку orElseGetоцінювання відбувається лише якщо oldValueпорожнє. orElseGetдозволяє ліниві оцінки.


4
Я витрачав багато разів через цю "дивну" поведінку ifElse (). Я б сказав, що має сенс віддавати перевагу ifElseGet () над ifElse ()
Енріко Джурін

3

Наступний приклад повинен демонструвати різницю:

String destroyTheWorld() {
  // destroy the world logic
  return "successfully destroyed the world";
}

Optional<String> opt = Optional.empty();

// we're dead
opt.orElse(destroyTheWorld());

// we're safe    
opt.orElseGet(() -> destroyTheWorld());

Відповідь з’являється і в документах.

public T orElseGet(Supplier<? extends T> other):

Поверніть значення, якщо воно є, інакше викликайте інше і поверніть результат цього виклику.

Supplier Чи не буде викликатися , якщо на Optionalподарунки. тоді як

public T orElse(T other):

Поверніть значення, якщо воно є, інакше поверніть інше.

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


3

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

Кращий спосіб зрозуміти різницю між orElse()і в orElseGet()тому , що orElse()завжди буде виконуватися , якщо Optional<T>є нуль або немає , але orElseGet()буде виконуватися тільки тоді , коли Optional<T>є нуль .

Значення словника orElse таке : - виконати частину, коли чогось немає, але тут це суперечить, див. Нижче приклад:

    Optional<String> nonEmptyOptional = Optional.of("Vishwa Ratna");
    String value = nonEmptyOptional.orElse(iAmStillExecuted());

    public static String iAmStillExecuted(){
    System.out.println("nonEmptyOptional is not NULL,still I am being executed");
    return "I got executed";
    }

Вихід: nonEmptyOtional не NULL, я все одно виконується


    Optional<String> emptyOptional = Optional.ofNullable(null);
    String value = emptyOptional.orElse(iAmStillExecuted());
    public static String iAmStillExecuted(){
    System.out.println("emptyOptional is NULL, I am being executed, it is normal as 
    per dictionary");
    return "I got executed";
    }

Вихідні дані : emptyOtional є NULL, я виконується, це нормально, відповідно до словника

Бо orElseGet()метод використовується відповідно до значення словника, orElseGet()частина буде виконана лише тоді, коли необов'язковий є нульовим .

Орієнтири :

+--------------------+------+-----+------------+-------------+-------+
| Benchmark          | Mode | Cnt | Score      | Error       | Units |
+--------------------+------+-----+------------+-------------+-------+
| orElseBenchmark    | avgt | 20  | 60934.425  | ± 15115.599 | ns/op |
+--------------------+------+-----+------------+-------------+-------+
| orElseGetBenchmark | avgt | 20  | 3.798      | ± 0.030     | ns/op |
+--------------------+------+-----+------------+-------------+-------+

Зауваження : orElseGet()явно перевершив orElse()наш конкретний приклад.

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


2

Перш за все перевірити декларацію обох методів.

1) OrElse: виконайте логіку та передайте результат як аргумент.

public T orElse(T other) {    
 return value != null ? value : other;
}

2) OrElseGet: Виконайте логіку, якщо значення всередині необов'язкового є нульовим

public T orElseGet(Supplier<? extends T> other) {
  return value != null ? value : other.get(); 
}

Деякі пояснення вищенаведеного оголошення: Аргумент "Optional.orElse" завжди виконується незалежно від значення об'єкта в необов'язковому (нульовому, порожньому або зі значенням). Завжди враховуйте вищезазначений момент, маючи на увазі під час використання "Optional.orElse", інакше використання "Optional.orElse" може бути дуже ризикованим у наступній ситуації.

Ризик-1) Проблема ведення журналу: Якщо вміст всередині orElse містить будь-який випадок журналу: у цьому випадку ви будете щоразу реєструвати його.

Optional.of(getModel())
   .map(x -> {
      //some logic
   })
  .orElse(getDefaultAndLogError());

getDefaultAndLogError() {
  log.error("No Data found, Returning default");
  return defaultValue;
}

Ризик-2) Випуск продуктивності: Якщо вміст всередині orElse є трудомістким: вміст, який вимагає часу, може бути будь-якими операціями вводу-виводу DB, викликом API, зчитуванням файлів. Якщо ми помістимо такий вміст у orElse (), система в кінцевому підсумку виконає код без використання.

Optional.of(getModel())
   .map(x -> //some logic)
   .orElse(getDefaultFromDb());

getDefaultFromDb() {
   return dataBaseServe.getDefaultValue(); //api call, db call.
}

Ризик-3) Неправомірний стан або помилка: Якщо вміст всередині orElse мутує якийсь стан об'єкта: ми можемо використовувати один і той же об’єкт в іншому місці, скажімо, у функції Optional.map, і це може поставити нас у критичну помилку.

List<Model> list = new ArrayList<>();
Optional.of(getModel())
  .map(x -> {
  })
  .orElse(get(list));

get(List < String > list) {
   log.error("No Data found, Returning default");
   list.add(defaultValue);
   return defaultValue;
}

Потім, коли ми можемо піти з orElse ()? Віддайте перевагу використанню orElse, коли значенням за замовчуванням є якийсь постійний об'єкт, enum. У всіх вищезазначених випадках ми можемо використовувати Optional.orElseGet () (який виконується лише тоді, коли необов'язково містить непорожнє значення) замість Optional.orElse (). Чому ?? У orElse ми передаємо значення результату за замовчуванням, але в orElseGet ми передаємо Постачальника, а метод Постачальника виконує лише, якщо значення в Додатковому є нульовим.

Ключові відходи від цього:

  1. Не використовуйте "Optional.orElse", якщо він містить будь-який запис журналу.
  2. Не використовуйте "Optional.orElse", якщо він містить логіку, що вимагає часу.
  3. Не використовуйте "Optional.orElse", якщо він мутує певний стан об'єкта.
  4. Використовуйте "Optional.orElse", якщо нам доведеться повертати константу, перерахунок.
  5. Віддайте перевагу "Необов’язково.орЕльсеГет" у ситуаціях, зазначених у 1,2 та 3-му пунктах.

Я пояснив це у пункті 2 ( "Необов’язково. Карта / опціональний.орЕльз"! = "Якщо / ще" ) у своєму середньому блозі. Використовуйте Java8 як програміст, а не як кодер


0

Зважаючи на наступний код:

import java.util.Optional;

// one class needs to have a main() method
public class Test
{
  public String orelesMethod() {
    System.out.println("in the Method");
    return "hello";
  }

  public void test() {
    String value;
    value = Optional.<String>ofNullable("test").orElseGet(this::orelesMethod);
    System.out.println(value); 

    value = Optional.<String>ofNullable("test").orElse(orelesMethod());
    System.out.println(value); 
  }

  // arguments are passed using the text field below this editor
  public static void main(String[] args)
  {
    Test test = new Test();

    test.test();
  }
}

якщо ми отримуємо valueтаким чином: Optional.<String>ofNullable(null)немає ніякої різниці між orElseGet () і OrElse (), але якщо ми отримаємо valueтаким чином: Optional.<String>ofNullable("test"), orelesMethod()в orElseGet()не називатиме , але в orElse()ньому буде називатися

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