`Optional.orElse()` 和 `Optional.orElseGet()` 之间的区别

Difference between `Optional.orElse()` and `Optional.orElseGet()`

我想了解 Optional<T>.orElse() and Optional<T>.orElseGet() 方法之间的区别。

orElse() 方法的描述是 "Return the value if present, otherwise return other."

orElseGet() 方法的描述是 "Return the value if present, otherwise invoke other and return the result of that invocation."

orElseGet() 方法采用 Supplier 功能接口,该接口基本上不采用任何参数和 returns T

在什么情况下需要使用orElseGet()?如果你有一个方法 T myDefault() 你为什么不直接做 optional.orElse(myDefault()) 而不是 optional.orElseGet(() -> myDefault()) 呢?

orElseGet() 似乎没有将 lambda 表达式的执行推迟到稍后的某个时间或其他什么时间,那么它有什么意义呢? (我原以为如果它返回一个更安全的 Optional<T> 会更有用,它的 get() 永远不会抛出 NoSuchElementException 并且 isPresent() 总是 returns true.. .但显然不是,它只是 returns T 就像 orElse()).

我还缺少其他一些区别吗?

以这两种情况为例:

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

如果opt不包含值,两者确实是等价的。但是如果 opt 包含一个值,将创建多少 Foo 个对象?

P.s.: 当然,在这个例子中,差异可能无法衡量,但是如果您必须从远程 Web 服务获取默认值,或者从一个数据库,一下子就变得很重要了。

我来到这里是为了解决 Kudo 提到的问题。

我正在为其他人分享我的经验。

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 是懒惰的。

我想说 orElseorElseGet 之间的最大区别在于我们想要评估某些东西以获得 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 允许延迟计算。

简答:

  • 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 (Redundant call)
    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 (Redundant call)");
        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");
    }
}

考虑以下代码:

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();
  }
}

如果我们这样得到valueOptional.<String>ofNullable(null),orElseGet()和orElse()没有区别,但是如果我们这样得到valueOptional.<String>ofNullable("test")orelesMethod()orElseGet() 中不会被调用,但在 orElse() 中会被调用

下面的例子可以证明两者的区别:

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

Optional<String> opt = Optional.of("Save the world");

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

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

答案也出现在文档中。

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

Return the value if present, otherwise invoke other and return the result of that invocation.

如果 Optional 存在,Supplier 将不会被调用 。然而,

public T orElse(T other):

Return the value if present, otherwise return other.

如果 other 是 returns 字符串的方法,它将被调用,但如果 Optional 存在,则不会返回它的值。

差异非常细微,如果您不注意,就会以错误的方式使用它。

了解 orElse()orElseGet() 之间区别的最佳方法是,如果 Optional<T>null[=,则 orElse() 将始终执行44=]或者不是,但是orElseGet()只会在Optional<T>null时执行

字典中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";
    }

Output: nonEmptyOptional is not NULL,still I am being executed


    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";
    }

Output: emptyOptional is NULL, I am being executed, it is normal as per dictionary

For orElseGet() , The method goes as per dictionary meaning, The orElseGet() part will be executed only when the Optional is 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 |
+--------------------+------+-----+------------+-------------+-------+

Remarks: orElseGet() has clearly outperformed orElse() for our particular example.

希望它消除了像我这样想要非常基本的地面示例的人的疑虑:)

首先检查两个方法的声明。

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里面的内容是耗时的: 时间密集的内容可以是任意i/o操作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。在上述所有情况下,我们可以使用 Optional.orElseGet()(仅当 Optional 包含空值时才执行)而不是 Optional.orElse()。为什么??在 orElse 中,我们传递默认结果值,但在 orElseGet 中,我们传递 Supplier 并且 Supplier 的方法仅在 Optional 中的值为 null 时执行。

要点:

  1. 如果包含任何日志语句,请不要使用“Optional.orElse”。
  2. 如果包含时间密集型逻辑,请不要使用“Optional.orElse”。
  3. 如果它正在改变某些对象状态,请不要使用“Optional.orElse”。
  4. 如果我们必须 return 一个常数,枚举,请使用“Optional.orElse”。
  5. 第1,2点和第3点提到的情况更喜欢“Optional.orElseGet”

我在第 2 点(“可选。map/Optional.orElse”!=“if/else”)我的中型博客中对此进行了解释。 Use Java8 as a programmer not as a coder

它们都习惯于return一个Optional的默认值,但是如果使用一个方法来产生默认值:

  • orElse:总是执行该方法,如果 Optional 不为空
  • orElseGet:仅当 Optional 为空时执行它 (+ Performance!)

查看此示例(class 可选示例):

public static void main(String[] args) {

    Optional<String> optionalNotEmpty = Optional.of("StringVal");
    
    // orElse: getDefaultValue called (useless)
    System.out.println(optionalNotEmpty.orElse(getDefaultValue()));
    
    // orElseGet: getDefaultValue not called (better solution)
    System.out.println(optionalNotEmpty.orElseGet(OptionalExample::getDefaultValue));
}

public static String getDefaultValue() {
    System.out.println("called");
    return "default value";
}

输出:

called
StringVal
StringVal