【问题标题】:Difference between `Optional.orElse()` and `Optional.orElseGet()``Optional.orElse()` 和 `Optional.orElseGet()` 之间的区别
【发布时间】:2016-01-15 04:26:04
【问题描述】:

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

orElse() 方法的描述是“如果存在则返回值,否则返回其他值。”

orElseGet() 方法的描述是“如果存在则返回该值,否则调用其他值并返回该调用的结果。”

orElseGet() 方法采用 Supplier 功能接口,该接口本质上不带任何参数并返回 T

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

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

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

【问题讨论】:

  • 原因是当您使用orElseGet 时,它仅在值不存在时才调用供应商。
  • 嗯,好的。所以在orElse() 的情况下,myDefault() 方法仍然被调用,但是它的返回值没有被使用。
  • 赞成的问题,因为从我看到的误解或只是忘记使用 orElseGet() 可能会导致一些严重的错误:medium.com/alphadev-thoughts/…
  • 这里有一个很好的解释:baeldung.com/java-optional-or-else-vs-or-else-get

标签: java java-8 optional


【解决方案1】:

它们都用于为一个 Optional 返回默认值,但如果使用方法来生成默认值:

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

看这个例子(类 OptionalExample):

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

【讨论】:

    【解决方案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”可能会非常危险。

    Risk-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. 如果我们必须返回一个常量枚举,请使用“Optional.orElse”。
    5. 在第 1,2 和第 3 点提到的情况下首选“Optional.orElseGet”。

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

    【讨论】:

    • 优秀的答案:)
    【解决方案3】:

    我来到这里是为了解决提到的问题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 是懒惰的。

    【讨论】:

    • 这不是“问题”。一个简单的事实是方法的参数在方法执行之前被评估。如果您将B() 传递给名为orElse()abc() 的方法,则不会产生任何影响,B() 会被评估。
    • 这里的问题实际上是方法的命名。 or 前缀误导开发人员(包括我在问这个问题时)认为这是一个短路操作,因为这是我们在布尔条件下所习惯的。然而,它不是,它只是一个在其前缀中有or 的方法名称,因此它的参数将被评估,而不管Optional 是否带有一个值。不幸的是,命名令人困惑,我们对此无能为力。
    • 感谢您的回答。必须说语言设计者并没有考虑到这会造成多么混乱。
    【解决方案4】:

    简答:

    • 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() == trueorElse()不管你是否愿意,总是调用后续函数。

    最后,使用的测试用例如下:

    结果:

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

    【讨论】:

    • 我认为您的图片可能有错误,应该在右侧显示“orElseGet”?除此之外,很好的例子。
    • 是的,你是对的。谢谢 :) 我会在接下来的几个小时内更新它
    • 对于第二个要点,似乎应该改为Optional.isPresent() == false(错误,不正确)
    • 很好的例子 - 但我真的不明白 Optional.orElse 声明 If a value is present, returns the value, otherwise returns other 的 Javadocs 如何暗示这种行为......
    • 根据您的解释,对我来说,orElse() 的行为类似于try-catch 表达式中的finally。我说的对吗?
    【解决方案5】:

    下面的例子应该能说明区别:

    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&lt;? extends T&gt; other):

    如果存在则返回值,否则调用其他并返回 该调用的结果。

    Supplier 不会在出现 Optional 时被调用。而,

    public T orElse(T other):

    如果存在则返回值,否则返回其他值。

    如果other是一个返回字符串的方法,它会被调用,但在Optional存在的情况下不会返回它的值。

    【讨论】:

    • 在给定的例子中,“我们已经死了”在这两种情况下。可选 opt = Optional.empty();将返回值为 null 的可选。 orElse 内部的逻辑 value != null ?价值:其他。 orElseGet 内部逻辑值!= null ?值:供应商.get()。在给定的示例中,两者都调用 destroyTheWorld()
    • @ABHIJITHKINI 在第二种情况下,该方法不会实际执行。别担心,我拯救了世界。
    • 在给定的例子中,它会;请在下面执行: Optional opt = Optional.empty(); opt.orElseGet(() -> destroyTheWorld());在控制台中:成功摧毁了世界,因为 opt 值为 NULL。
    【解决方案6】:

    区别是非常微妙的,如果你不注意,那么你会一直以错误的方式使用它。

    了解orElse()orElseGet() 之间区别的最佳方法是,如果Optional&lt;T&gt;nullnot,则始终执行orElse(),但orElseGet() 只会在Optional&lt;T&gt;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";
        }
    

    输出: 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() 部分仅在 Optional 为 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()

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

    【讨论】:

      【解决方案7】:

      考虑以下代码:

      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.&lt;String&gt;ofNullable(null),orElseGet()和orElse()没有区别,但是如果我们这样得到valueOptional.&lt;String&gt;ofNullable("test")orelesMethod() in @ 987654327@ 不会被调用,但在orElse() 中会被调用

      【讨论】:

        【解决方案8】:

        采取这两种情况:

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

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

        Ps:当然,在这个例子中,差异可能无法测量,但如果您必须从远程 Web 服务或数据库中获取默认值,它突然变得非常重要。

        【讨论】:

        • 感谢各位的澄清。因此,差异是微妙的,但意义重大。在第二种情况下,它不会创建新的Foo 对象,而在第一种情况下,它会创建它,但如果Optional 中有值,则不使用它。
        • @jbx 是的,在我点头的示例中,它可以说没有任何真正的区别,但是如果您必须从远程 Web 服务或数据库中获取默认值,则差异突然变得非常重要。
        • @jbx:你把两件事混为一谈了。关于奇怪的基准测试结果已经存在关于 SO 的问题,这仅仅是由于不使用计算结果造成的。 JVM可以做到这一点。另一方面,System.out.println()不是计算,而是产生可观察到的副作用的语句。而且我已经说过,可观察到的副作用会阻碍优化(控制台输出流外部资源)。
        • 这是我第一次看到问题而不是答案被接受。
        • 例如,如果您必须从远程 Web 服务获取默认值”,这正是我的场景。在我的例子中,可选的是查询,在没有查询的情况下默认是获取所有值......是的,orElseGet 将该操作的运行时间减少了 1000 倍。
        【解决方案9】:

        我想说orElseorElseGet 之间的最大区别在于我们想要评估某些东西以获得else 条件中的新值。

        考虑这个简单的例子 -

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

        现在让我们将上面的示例转换为使用OptionalorElse

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

        现在让我们将上面的示例转换为使用OptionalorElseGet

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

        orElse 被调用时,apicall().value 被评估并传递给方法。而在orElseGet 的情况下,仅当oldValue 为空时才会进行评估。 orElseGet 允许惰性求值。

        【讨论】:

        • 由于 ifElse() 的这种“奇怪”行为,我浪费了很多时间。我会说喜欢 ifElseGet() 而不是 ifElse() 是有道理的
        猜你喜欢
        • 2017-10-30
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-12-25
        • 2020-05-10
        • 2014-09-20
        • 2010-10-28
        • 2015-10-04
        相关资源
        最近更新 更多