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


206

我试图了解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 ...但显然它不是,它只是返回TorElse())。

我还有其他区别吗?


7
原因是当您使用orElseGet它时,仅当缺少价值时才呼叫供应商。
Alex Salauyou 2015年

9
好的,知道了。所以在的情况下,orElse()myDefault()方法仍称,但只是不使用它的返回值。
jbx

3
提出了质疑,因为从我所看到的误会或仅仅忘记使用它orElseGet()可能会导致一些严重的错误:medium.com/alphadev-thoughts/…–
softarn

在这里可以找到很好的解释:baeldung.com/java-optional-or-else-vs-or-else-get
Nestor Milyaev

Answers:


172

采取以下两种情况:

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

如果opt不包含值,则两者实际上是等效的。但是,如果opt 包含值,有多少Foo对象将被创造出来的?

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


22
感谢您的澄清。因此,差异是微妙的,但意义重大。在第二种情况下,它不会创建新Foo对象,而在第一种情况下,它将创建它,但如果内有一个值,则不会使用它Optional
jbx

5
@jbx是的,并且在我的点头示例中,它可以说并没有什么真正的区别,但是,如果您必须从例如远程Web服务或数据库中获取默认值,则区别突然变得非常重要。
biziclop

2
@jbx:您正在混淆两件事。在SO上已经有关于奇怪的基准测试结果的问题,这些测试结果仅仅是由于不使用计算结果而引起的。JVM 可以做到这一点。在另一方面,System.out.println()计算,但产生可观察到的副作用的声明。我已经说过,可观察到的副作用会阻碍优化(控制台输出流外部资源)。
Holger

7
那是我第一次看到一个问题而不是答案。
Kirill G.

4
“例如,如果您必须从远程Web服务获取默认值 ”,这正是我的情况。在我的情况下,可选的是查询,而在没有查询的情况下,默认值是获取所有值...是的,否则,ElseGet将该操作的运行时间减少了1000倍。
scottysseus

109

简短答案:

  • 否则容易()总是会调用指定的函数,你是否希望与否,无论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
    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”?除此之外,很好的例子。
Yalla T.

是的,你是对的。谢谢:)我将在接下来的几个小时内更新它
nxhoaf

对于第二个要点,似乎应该Optional.isPresent() == false改为(假,不是真)
曼努埃尔·乔丹

很好的例子-但是我真的不明白Optional.orElse哪个州的Javadocs If a value is present, returns the value, otherwise returns other可以暗示这种行为……
Erik Finnman

根据你的解释,对我来说,它看起来像orElse()类似的行为finallytry-catch表达。我对么?
Mike B.19年

63

我到达这里的原因是工藤提到的问题。

我正在与他人分享我的经验。

orElseorElseGet,这是一个问题:

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()的这种“奇怪”行为,我浪费了很多时间。我想说,与ifElse()相比,优先选择ifElseGet()
Enrico Giurin

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()仅当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()其他示例。

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


2

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

1)OrElse:执行逻辑并将结果作为参数传递。

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

2)OrElseGet:如果可选值内的值为null,则执行逻辑

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操作数据库调用,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()? 当默认值为某些常量对象enum时,最好使用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”。

我已经在我的中等博客的第二点(“ 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()和否则容易()没有什么区别,但如果我们value以这样的方式Optional.<String>ofNullable("test")orelesMethod()orElseGet()不会被调用,但orElse()它会被称为

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.