Разница между `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()всегда возвращает true ... но, очевидно, это не так, он просто возвращает Tкак orElse()).

Есть ли какая-то другая разница, по которой я скучаю?

JBX
источник
7
Причина в том, что при использовании orElseGetон вызывает поставщика, только если значение отсутствует.
Алекс Салаю
9
Ах, хорошо, понял. Таким образом , в случае orElse()с myDefault()методом еще называют, но ее возвращаемое значение просто не используется.
jbx
3
Upvoted вопроса , потому что от того, что я видел недоразумение или просто забываю использование orElseGet()может привести к некоторым серьезным ошибкам: medium.com/alphadev-thoughts/...
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объектов будет создано?

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

biziclop
источник
22
Спасибо за разъяснения, ребята. Таким образом, разница невелика, но значительна. Во втором случае он не будет создавать новый Fooобъект, в то время как в первом случае он будет создавать его, но не будет использовать его, если внутри значения есть значение Optional.
jbx
5
@jbx Да, и в моем простом примере это, вероятно, не имеет никакого значения, но если вам нужно получить значение по умолчанию, например, из удаленного веб-сервиса или из базы данных, разница внезапно становится очень важной.
Бизиклоп
2
@jbx: вы смешиваете две вещи. На SO уже есть вопросы относительно странных результатов тестов, которые просто были вызваны неиспользованием результатов вычислений. 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");
    }
}
nxhoaf
источник
Я думаю, что у вас может быть Ошибка на вашей картинке, она должна сказать "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позволяет ленивые оценки.

devang
источник
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):

Верните значение, если оно присутствует, в противном случае вызовите 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";
    }

Вывод: nonEmptyOptional не 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";
    }

Вывод : emptyOptional равен NULL, я выполняюсь, это нормально по словарю

Для orElseGet(), метод идет в соответствии со значением словаря, orElseGet()часть будет выполняться только тогда, когда необязательный имеет значение null .

Контрольные показатели :

+--------------------+------+-----+------------+-------------+-------+
| 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» всегда выполняется независимо от значения объекта в необязательном порядке (null, пусто или со значением). При использовании «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 требует много времени : Содержимое, требующее много времени, может представлять собой любые операции ввода-вывода, вызов БД, вызов 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 содержит непустое значение) вместо Optional.orElse (). Зачем?? В orElse мы передаем значение результата по умолчанию, но в orElseGet мы передаем Supplier, а метод Supplier выполняется только в том случае, если значение в Optional равно null.

Основные выводы из этого:

  1. Не используйте «Optional.orElse», если он содержит какой-либо оператор журнала.
  2. Не используйте «Optional.orElse», если он содержит лояльную по времени логику.
  3. Не используйте «Optional.orElse», если он изменяет некоторое состояние объекта.
  4. Используйте «Optional.orElse», если нам нужно вернуть константу, enum.
  5. Предпочитайте «Optional.orElseGet» в ситуациях, упомянутых в 1,2 и 3 пунктах.

Я объяснил это в пункте 2 ( «Optional.map/Optional.orElse»! = «If / else» ) моего среднего блога. Используйте 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()нем будет называться

ratzip
источник