Guide To Java 8 Optional – Java 8备选指南

最后修改: 2016年 12月 8日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this tutorial, we’re going to show the Optional class that was introduced in Java 8.

在本教程中,我们将展示Java 8中引入的Optional类。

The purpose of the class is to provide a type-level solution for representing optional values instead of null references.

该类的目的是提供一个类型级的解决方案,用于表示可选值而不是null引用。

To get a deeper understanding of why we should care about the Optional class, take a look at the official Oracle article.

为了更深入地了解我们为什么要关心Optional类,请看Oracle官方文章

2. Creating Optional Objects

2.创建可选的对象

There are several ways of creating Optional objects.

有几种创建Optional对象的方法。

To create an empty Optional object, we simply need to use its empty() static method:

要创建一个空的Optional对象,我们只需要使用其empty()静态方法。

@Test
public void whenCreatesEmptyOptional_thenCorrect() {
    Optional<String> empty = Optional.empty();
    assertFalse(empty.isPresent());
}

Note that we used the isPresent() method to check if there is a value inside the Optional object. A value is present only if we have created Optional with a non-null value. We’ll look at the isPresent() method in the next section.

请注意,我们使用isPresent()方法来检查Optional对象内是否有一个值。只有当我们创建的Optional具有非null值时,值才会存在。我们将在下一节看一下isPresent()方法。

We can also create an Optional object with the static method of():

我们也可以用静态方法of()创建一个Optional对象。

@Test
public void givenNonNull_whenCreatesNonNullable_thenCorrect() {
    String name = "baeldung";
    Optional<String> opt = Optional.of(name);
    assertTrue(opt.isPresent());
}

However, the argument passed to the of() method can’t be null. Otherwise, we’ll get a NullPointerException:

然而,传递给of()方法的参数不能是null.,否则,我们将得到一个NullPointerException

@Test(expected = NullPointerException.class)
public void givenNull_whenThrowsErrorOnCreate_thenCorrect() {
    String name = null;
    Optional.of(name);
}

But in case we expect some null values, we can use the ofNullable() method:

但是如果我们期待一些null值,我们可以使用ofNullable()方法。

@Test
public void givenNonNull_whenCreatesNullable_thenCorrect() {
    String name = "baeldung";
    Optional<String> opt = Optional.ofNullable(name);
    assertTrue(opt.isPresent());
}

By doing this, if we pass in a null reference, it doesn’t throw an exception but rather returns an empty Optional object:

通过这样做,如果我们传入一个null引用,它不会抛出一个异常,而是返回一个空的Optional对象。

@Test
public void givenNull_whenCreatesNullable_thenCorrect() {
    String name = null;
    Optional<String> opt = Optional.ofNullable(name);
    assertFalse(opt.isPresent());
}

3. Checking Value Presence: isPresent() and isEmpty()

3.检查值的存在 isPresent()isEmpty()

When we have an Optional object returned from a method or created by us, we can check if there is a value in it or not with the isPresent() method:

当我们有一个Optional对象从一个方法返回或由我们创建时,我们可以用isPresent()方法检查其中是否有一个值。

@Test
public void givenOptional_whenIsPresentWorks_thenCorrect() {
    Optional<String> opt = Optional.of("Baeldung");
    assertTrue(opt.isPresent());

    opt = Optional.ofNullable(null);
    assertFalse(opt.isPresent());
}

This method returns true if the wrapped value is not null.

如果包裹的值不是null,该方法返回true

Also, as of Java 11, we can do the opposite with the isEmpty method:

另外,从Java 11开始,我们可以用isEmpty方法做相反的事情。

@Test
public void givenAnEmptyOptional_thenIsEmptyBehavesAsExpected() {
    Optional<String> opt = Optional.of("Baeldung");
    assertFalse(opt.isEmpty());

    opt = Optional.ofNullable(null);
    assertTrue(opt.isEmpty());
}

4. Conditional Action With ifPresent()

4.使用ifPresent()的条件行动

The ifPresent() method enables us to run some code on the wrapped value if it’s found to be non-null. Before Optional, we’d do:

ifPresent()方法使我们能够在发现被包装的值非null时对其运行一些代码。在Optional之前,我们会这样做。

if(name != null) {
    System.out.println(name.length());
}

This code checks if the name variable is null or not before going ahead to execute some code on it. This approach is lengthy, and that’s not the only problem — it’s also prone to error.

这段代码在继续对其执行一些代码之前,会检查name变量是否为null。这种方法很冗长,而且这还不是唯一的问题–它还容易出错。

Indeed, what guarantees that after printing that variable, we won’t use it again and then forget to perform the null check?

事实上,什么能保证在打印该变量后,我们不会再次使用它,然后忘记进行空值检查?

This can result in a NullPointerException at runtime if a null value finds its way into that code. When a program fails due to input issues, it’s often a result of poor programming practices.

如果一个空值进入该代码,这可能会在运行时导致NullPointerException当一个程序由于输入问题而失败时,这往往是不良编程实践的结果。

Optional makes us deal with nullable values explicitly as a way of enforcing good programming practices.

Optional使我们明确地处理可归零的值,作为强制执行良好编程实践的一种方式。

Let’s now look at how the above code could be refactored in Java 8.

现在让我们来看看在Java 8中如何重构上述代码。

In typical functional programming style, we can execute perform an action on an object that is actually present:

在典型的函数式编程风格中,我们可以对一个实际存在的对象执行动作。

@Test
public void givenOptional_whenIfPresentWorks_thenCorrect() {
    Optional<String> opt = Optional.of("baeldung");
    opt.ifPresent(name -> System.out.println(name.length()));
}

In the above example, we use only two lines of code to replace the five that worked in the first example: one line to wrap the object into an Optional object and the next to perform implicit validation as well as execute the code.

在上面的例子中,我们只用了两行代码来代替第一个例子中的五行代码:一行是将对象包装成Optional对象,另一行是执行隐式验证以及执行代码。

5. Default Value With orElse()

5.使用orElse()的默认值

The orElse() method is used to retrieve the value wrapped inside an Optional instance. It takes one parameter, which acts as a default value. The orElse() method returns the wrapped value if it’s present, and its argument otherwise:

orElse()方法被用来检索包裹在Optional实例中的值。它需要一个参数,作为一个默认值。orElse()方法如果存在包装的值,就返回它,否则就返回它的参数。

@Test
public void whenOrElseWorks_thenCorrect() {
    String nullName = null;
    String name = Optional.ofNullable(nullName).orElse("john");
    assertEquals("john", name);
}

6. Default Value With orElseGet()

6.使用orElseGet()的默认值

The orElseGet() method is similar to orElse(). However, instead of taking a value to return if the Optional value is not present, it takes a supplier functional interface, which is invoked and returns the value of the invocation:

orElseGet()方法与orElse()类似。然而,如果Optional值不存在,它不是取一个值来返回,而是取一个供应商功能接口,它被调用并返回调用的值。

@Test
public void whenOrElseGetWorks_thenCorrect() {
    String nullName = null;
    String name = Optional.ofNullable(nullName).orElseGet(() -> "john");
    assertEquals("john", name);
}

7. Difference Between orElse and orElseGet()

7、orElseorElseGet()之间的区别

To a lot of programmers who are new to Optional or Java 8, the difference between orElse() and orElseGet() is not clear. As a matter of fact, these two methods give the impression that they overlap each other in functionality.

对于很多刚接触Optional或Java 8的程序员来说,orElse()orElseGet()之间的区别并不明显。事实上,这两个方法给人的印象是它们在功能上相互重叠。

However, there’s a subtle but very important difference between the two that can affect the performance of our code drastically if not well understood.

然而,这两者之间有一个微妙但非常重要的区别,如果没有很好地理解,会极大地影响我们代码的性能。

Let’s create a method called getMyDefault() in the test class, which takes no arguments and returns a default value:

让我们在测试类中创建一个名为getMyDefault()的方法,它不需要参数,并返回一个默认值。

public String getMyDefault() {
    System.out.println("Getting Default Value");
    return "Default Value";
}

Let’s see two tests and observe their side effects to establish both where orElse() and orElseGet() overlap and where they differ:

让我们看看两个测试并观察它们的副作用,以确定orElse()orElseGet()的重叠之处以及它们的区别。

@Test
public void whenOrElseGetAndOrElseOverlap_thenCorrect() {
    String text = null;

    String defaultText = Optional.ofNullable(text).orElseGet(this::getMyDefault);
    assertEquals("Default Value", defaultText);

    defaultText = Optional.ofNullable(text).orElse(getMyDefault());
    assertEquals("Default Value", defaultText);
}

In the above example, we wrap a null text inside an Optional object and attempt to get the wrapped value using each of the two approaches.

在上面的例子中,我们在一个Optional对象中包裹了一个空文本,并试图用两种方法中的每一种来获取包裹的值。

The side effect is:

副作用是。

Getting default value...
Getting default value...

The getMyDefault() method is called in each case. It so happens that when the wrapped value is not present, then both orElse() and orElseGet() work exactly the same way.

getMyDefault()方法在每种情况下都被调用。碰巧的是,当包裹的值不存在时,那么orElse()orElseGet()的工作方式完全相同。

Now let’s run another test where the value is present, and ideally, the default value should not even be created:

现在让我们运行另一个测试,其中的值是存在的,理想情况下,甚至不应该创建默认值。

@Test
public void whenOrElseGetAndOrElseDiffer_thenCorrect() {
    String text = "Text present";

    System.out.println("Using orElseGet:");
    String defaultText 
      = Optional.ofNullable(text).orElseGet(this::getMyDefault);
    assertEquals("Text present", defaultText);

    System.out.println("Using orElse:");
    defaultText = Optional.ofNullable(text).orElse(getMyDefault());
    assertEquals("Text present", defaultText);
}

In the above example, we are no longer wrapping a null value, and the rest of the code remains the same.

在上面的例子中,我们不再包装一个null值,代码的其余部分保持不变。

Now let’s take a look at the side effect of running this code:

现在让我们来看看运行这段代码的副作用。

Using orElseGet:
Using orElse:
Getting default value...

Notice that when using orElseGet() to retrieve the wrapped value, the getMyDefault() method is not even invoked since the contained value is present.

请注意,当使用orElseGet()来检索被包装的值时,getMyDefault()方法甚至没有被调用,因为包含的值已经存在。

However, when using orElse(), whether the wrapped value is present or not, the default object is created. So in this case, we have just created one redundant object that is never used.

然而,当使用orElse()时,无论包裹的值是否存在,都会创建默认对象。所以在这种情况下,我们只是创建了一个多余的对象,而这个对象永远不会被使用。

In this simple example, there is no significant cost to creating a default object, as the JVM knows how to deal with such. However, when a method such as getMyDefault() has to make a web service call or even query a database, the cost becomes very obvious.

在这个简单的例子中,创建默认对象并没有明显的成本,因为 JVM 知道如何处理这种情况。然而,当诸如getMyDefault()这样的方法需要调用网络服务甚至查询数据库时,成本就变得非常明显。

8. Exceptions With orElseThrow()

8.使用orElseThrow()的异常

The orElseThrow() method follows from orElse() and orElseGet() and adds a new approach for handling an absent value.

orElseThrow()方法继承了orElse()orElseGet(),并增加了一个新的方法来处理一个不存在的值。

Instead of returning a default value when the wrapped value is not present, it throws an exception:

当包裹的值不存在时,它不会返回一个默认值,而是抛出一个异常。

@Test(expected = IllegalArgumentException.class)
public void whenOrElseThrowWorks_thenCorrect() {
    String nullName = null;
    String name = Optional.ofNullable(nullName).orElseThrow(
      IllegalArgumentException::new);
}

Method references in Java 8 come in handy here, to pass in the exception constructor.

Java 8中的方法引用在这里派上了用场,可以传递给异常构造函数。

Java 10 introduced a simplified no-arg version of orElseThrow() method. In case of an empty Optional it throws a NoSuchElementException:

Java 10引入了一个简化的无参数版本的orElseThrow()方法。如果有一个空的Optional,它会抛出一个NoSuchElementException

@Test(expected = NoSuchElementException.class)
public void whenNoArgOrElseThrowWorks_thenCorrect() {
    String nullName = null;
    String name = Optional.ofNullable(nullName).orElseThrow();
}

9. Returning Value With get()

9.用get()返回值

The final approach for retrieving the wrapped value is the get() method:

检索包装值的最终方法是get()方法。

@Test
public void givenOptional_whenGetsValue_thenCorrect() {
    Optional<String> opt = Optional.of("baeldung");
    String name = opt.get();
    assertEquals("baeldung", name);
}

However, unlike the previous three approaches, get() can only return a value if the wrapped object is not null; otherwise, it throws a no such element exception:

然而,与前三种方法不同,get()只能在被包装的对象不是null时返回一个值;否则,它会抛出一个无此元素的异常。

@Test(expected = NoSuchElementException.class)
public void givenOptionalWithNull_whenGetThrowsException_thenCorrect() {
    Optional<String> opt = Optional.ofNullable(null);
    String name = opt.get();
}

This is the major flaw of the get() method. Ideally, Optional should help us avoid such unforeseen exceptions. Therefore, this approach works against the objectives of Optional and will probably be deprecated in a future release.

这就是get()方法的主要缺陷。理想情况下,Optional 应该帮助我们避免这种不可预见的异常。因此,这种方法违背了Optional的目标,并且可能会在未来的版本中被弃用。

So, it’s advisable to use the other variants that enable us to prepare for and explicitly handle the null case.

因此,建议使用其他变体,使我们能够准备并明确地处理null情况。

10. Conditional Return With filter()

10.使用filter()的条件性返回

We can run an inline test on our wrapped value with the filter method. It takes a predicate as an argument and returns an Optional object. If the wrapped value passes testing by the predicate, then the Optional is returned as-is.

我们可以用filter方法对我们的包装值运行内联测试。它接收一个谓词作为参数,并返回一个Optional对象。如果被包装的值通过了谓词的测试,那么Optional将被原样返回。

However, if the predicate returns false, then it will return an empty Optional:

然而,如果该谓词返回false,那么它将返回一个空的Optional

@Test
public void whenOptionalFilterWorks_thenCorrect() {
    Integer year = 2016;
    Optional<Integer> yearOptional = Optional.of(year);
    boolean is2016 = yearOptional.filter(y -> y == 2016).isPresent();
    assertTrue(is2016);
    boolean is2017 = yearOptional.filter(y -> y == 2017).isPresent();
    assertFalse(is2017);
}

The filter method is normally used this way to reject wrapped values based on a predefined rule. We could use it to reject a wrong email format or a password that is not strong enough.

filter方法通常是这样使用的,根据预定义的规则拒绝包裹的值。我们可以用它来拒绝一个错误的电子邮件格式或一个不够强的密码。

Let’s look at another meaningful example. Say we want to buy a modem, and we only care about its price.

让我们看看另一个有意义的例子。假设我们想买一个调制解调器,而我们只关心它的价格。

We receive push notifications on modem prices from a certain site and store these in objects:

我们从某个网站接收关于调制解调器价格的推送通知,并将这些通知存储在对象中。

public class Modem {
    private Double price;

    public Modem(Double price) {
        this.price = price;
    }
    // standard getters and setters
}

We then feed these objects to some code whose sole purpose is to check if the modem price is within our budget range.

然后我们将这些对象送入一些代码,其唯一目的是检查调制解调器的价格是否在我们的预算范围内。

Let’s now take a look at the code without Optional:

现在让我们看一下没有Optional的代码。

public boolean priceIsInRange1(Modem modem) {
    boolean isInRange = false;

    if (modem != null && modem.getPrice() != null 
      && (modem.getPrice() >= 10 
        && modem.getPrice() <= 15)) {

        isInRange = true;
    }
    return isInRange;
}

Pay attention to how much code we have to write to achieve this, especially in the if condition. The only part of the if condition that is critical to the application is the last price-range check; the rest of the checks are defensive:

请注意我们要写多少代码来实现这一点,特别是在if条件中。if条件中唯一对应用程序至关重要的部分是最后的价格范围检查;其余的检查是防御性的。

@Test
public void whenFiltersWithoutOptional_thenCorrect() {
    assertTrue(priceIsInRange1(new Modem(10.0)));
    assertFalse(priceIsInRange1(new Modem(9.9)));
    assertFalse(priceIsInRange1(new Modem(null)));
    assertFalse(priceIsInRange1(new Modem(15.5)));
    assertFalse(priceIsInRange1(null));
}

Apart from that, it’s possible to forget about the null checks over a long day without getting any compile-time errors.

除此以外,在很长一段时间内,我们有可能忘记空值检查,而不会出现任何编译时错误。

Now let’s look at a variant with Optional#filter:

现在让我们看一下带有Optional#filter的变体。

public boolean priceIsInRange2(Modem modem2) {
     return Optional.ofNullable(modem2)
       .map(Modem::getPrice)
       .filter(p -> p >= 10)
       .filter(p -> p <= 15)
       .isPresent();
 }

The map call is simply used to transform a value to some other value. Keep in mind that this operation does not modify the original value.

map调用只是用来将一个值转换为另一个值。请记住,这个操作并不修改原始值。

In our case, we are obtaining a price object from the Model class. We will look at the map() method in detail in the next section.

在我们的案例中,我们要从Model类中获取一个价格对象。我们将在下一节详细了解map()方法。

First of all, if a null object is passed to this method, we don’t expect any problem.

首先,如果一个null对象被传递给这个方法,我们预计不会有任何问题。

Secondly, the only logic we write inside its body is exactly what the method name describes — price-range check. Optional takes care of the rest:

其次,我们在其主体内编写的唯一逻辑正是方法名称所描述的–价格范围检查。Optional负责其余的工作。

@Test
public void whenFiltersWithOptional_thenCorrect() {
    assertTrue(priceIsInRange2(new Modem(10.0)));
    assertFalse(priceIsInRange2(new Modem(9.9)));
    assertFalse(priceIsInRange2(new Modem(null)));
    assertFalse(priceIsInRange2(new Modem(15.5)));
    assertFalse(priceIsInRange2(null));
}

The previous approach promises to check price range but has to do more than that to defend against its inherent fragility. Therefore, we can use the filter method to replace unnecessary if statements and reject unwanted values.

前面的方法承诺检查价格范围,但必须做得更多,以抵御其固有的脆弱性。因此,我们可以使用filter方法来替换不必要的if语句,拒绝不需要的值。

11. Transforming Value With map()

11.用map()转换价值

In the previous section, we looked at how to reject or accept a value based on a filter.

在上一节中,我们研究了如何根据一个过滤器拒绝或接受一个值。

We can use a similar syntax to transform the Optional value with the map() method:

我们可以使用类似的语法,用map()方法转换Optional值。

@Test
public void givenOptional_whenMapWorks_thenCorrect() {
    List<String> companyNames = Arrays.asList(
      "paypal", "oracle", "", "microsoft", "", "apple");
    Optional<List<String>> listOptional = Optional.of(companyNames);

    int size = listOptional
      .map(List::size)
      .orElse(0);
    assertEquals(6, size);
}

In this example, we wrap a list of strings inside an Optional object and use its map method to perform an action on the contained list. The action we perform is to retrieve the size of the list.

在这个例子中,我们将一个字符串列表包裹在一个Optional对象中,并使用其map方法对所包含的列表执行一个操作。我们执行的操作是检索列表的大小。

The map method returns the result of the computation wrapped inside Optional. We then have to call an appropriate method on the returned Optional to retrieve its value.

map方法返回包裹在Optional中的计算结果。然后我们必须在返回的Optional上调用一个适当的方法来检索其值。

Notice that the filter method simply performs a check on the value and returns an Optional describing this value only if it matches the given predicate. Otherwise returns an empty Optional. The map method however takes the existing value, performs a computation using this value, and returns the result of the computation wrapped in an Optional object:

请注意,filter方法只是对值进行检查,并且只有当它与给定的谓词相匹配时才返回描述这个值的Optional。否则就返回一个空的Optional。map方法则接收现有的值,使用这个值进行计算,并返回包裹在Optional对象里的计算结果。

@Test
public void givenOptional_whenMapWorks_thenCorrect2() {
    String name = "baeldung";
    Optional<String> nameOptional = Optional.of(name);

    int len = nameOptional
     .map(String::length)
     .orElse(0);
    assertEquals(8, len);
}

We can chain map and filter together to do something more powerful.

我们可以把mapfilter连在一起,做一些更强大的事情。

Let’s assume we want to check the correctness of a password input by a user. We can clean the password using a map transformation and check its correctness using a filter:

假设我们想检查用户输入的密码的正确性。我们可以使用map转换清理密码,并使用filter检查其正确性。

@Test
public void givenOptional_whenMapWorksWithFilter_thenCorrect() {
    String password = " password ";
    Optional<String> passOpt = Optional.of(password);
    boolean correctPassword = passOpt.filter(
      pass -> pass.equals("password")).isPresent();
    assertFalse(correctPassword);

    correctPassword = passOpt
      .map(String::trim)
      .filter(pass -> pass.equals("password"))
      .isPresent();
    assertTrue(correctPassword);
}

As we can see, without first cleaning the input, it will be filtered out — yet users may take for granted that leading and trailing spaces all constitute input. So, we transform a dirty password into a clean one with a map before filtering out incorrect ones.

我们可以看到,如果不先清理输入,就会被过滤掉–然而用户可能会想当然地认为前导和尾部的空格都构成输入。因此,我们用map将一个肮脏的密码转化为干净的密码,然后再过滤掉不正确的密码。

12. Transforming Value With flatMap()

12.用flatMap()转换值

Just like the map() method, we also have the flatMap() method as an alternative for transforming values. The difference is that map transforms values only when they are unwrapped whereas flatMap takes a wrapped value and unwraps it before transforming it.

就像map()方法一样,我们也有flatMap()方法作为转换值的替代品。不同的是,map只在值被解包时进行转换,而flatMap则是在转换值之前,先将其解包。

Previously, we created simple String and Integer objects for wrapping in an Optional instance. However, frequently, we will receive these objects from an accessor of a complex object.

之前,我们创建了简单的StringInteger对象,用于包裹在Optional实例中。然而,我们经常会从一个复杂对象的访问器中接收这些对象。

To get a clearer picture of the difference, let’s have a look at a Person object that takes a person’s details such as name, age and password:

为了更清楚地了解两者的区别,让我们看看一个Person对象,它接收一个人的详细信息,如姓名、年龄和密码。

public class Person {
    private String name;
    private int age;
    private String password;

    public Optional<String> getName() {
        return Optional.ofNullable(name);
    }

    public Optional<Integer> getAge() {
        return Optional.ofNullable(age);
    }

    public Optional<String> getPassword() {
        return Optional.ofNullable(password);
    }

    // normal constructors and setters
}

We would normally create such an object and wrap it in an Optional object just like we did with String.

我们通常会创建这样一个对象,并将其包裹在一个Optional对象中,就像我们对String所做的那样。

Alternatively, it can be returned to us by another method call:

另外,它也可以通过另一个方法调用返回给我们。

Person person = new Person("john", 26);
Optional<Person> personOptional = Optional.of(person);

Notice now that when we wrap a Person object, it will contain nested Optional instances:

现在注意到,当我们包裹一个Person对象时,它将包含嵌套的Optional实例。

@Test
public void givenOptional_whenFlatMapWorks_thenCorrect2() {
    Person person = new Person("john", 26);
    Optional<Person> personOptional = Optional.of(person);

    Optional<Optional<String>> nameOptionalWrapper  
      = personOptional.map(Person::getName);
    Optional<String> nameOptional  
      = nameOptionalWrapper.orElseThrow(IllegalArgumentException::new);
    String name1 = nameOptional.orElse("");
    assertEquals("john", name1);

    String name = personOptional
      .flatMap(Person::getName)
      .orElse("");
    assertEquals("john", name);
}

Here, we’re trying to retrieve the name attribute of the Person object to perform an assertion.

在这里,我们试图检索Person对象的name属性,以执行一个断言。

Note how we achieve this with map() method in the third statement, and then notice how we do the same with flatMap() method afterwards.

注意我们是如何在第三条语句中用map()方法实现这一目标的,然后注意我们是如何在之后用flatMap()方法做同样的事情的。

The Person::getName method reference is similar to the String::trim call we had in the previous section for cleaning up a password.

Person::getName方法引用类似于我们在上一节清理密码时的String::trim调用。

The only difference is that getName() returns an Optional rather than a String as did the trim() operation. This, coupled with the fact that a map transformation wraps the result in an Optional object, leads to a nested Optional.

唯一的区别是,getName()返回一个Optional,而不是像trim()操作那样返回一个字符串。再加上map转换将结果包裹在Optional对象中的事实,导致了嵌套的Optional

While using map() method, therefore, we need to add an extra call to retrieve the value before using the transformed value. This way, the Optional wrapper will be removed. This operation is performed implicitly when using flatMap.

因此,在使用map()方法时,我们需要在使用转换后的值之前添加一个额外的调用来检索该值。这样一来,Optional包装器就会被移除。在使用flatMap时,这个操作是隐式执行的。

13. Chaining Optionals in Java 8

13.在Java 8中串联Optionals

Sometimes, we may need to get the first non-empty Optional object from a number of Optionals. In such cases, it would be very convenient to use a method like orElseOptional(). Unfortunately, such operation is not directly supported in Java 8.

有时,我们可能需要从一些Optional中获取第一个非空的Optional对象。在这种情况下,使用类似orElseOptional()的方法会非常方便。不幸的是,这样的操作在Java 8中不被直接支持。

Let’s first introduce a few methods that we’ll be using throughout this section:

让我们首先介绍一下本节中我们将使用的一些方法。

private Optional<String> getEmpty() {
    return Optional.empty();
}

private Optional<String> getHello() {
    return Optional.of("hello");
}

private Optional<String> getBye() {
    return Optional.of("bye");
}

private Optional<String> createOptional(String input) {
    if (input == null || "".equals(input) || "empty".equals(input)) {
        return Optional.empty();
    }
    return Optional.of(input);
}

In order to chain several Optional objects and get the first non-empty one in Java 8, we can use the Stream API:

为了在Java 8中连锁几个Optional对象并获得第一个非空对象,我们可以使用Stream API。

@Test
public void givenThreeOptionals_whenChaining_thenFirstNonEmptyIsReturned() {
    Optional<String> found = Stream.of(getEmpty(), getHello(), getBye())
      .filter(Optional::isPresent)
      .map(Optional::get)
      .findFirst();
    
    assertEquals(getHello(), found);
}

The downside of this approach is that all of our get methods are always executed, regardless of where a non-empty Optional appears in the Stream.

这种方法的缺点是我们所有的get方法总是被执行,无论非空的Optional出现在Stream的什么地方。

If we want to lazily evaluate the methods passed to Stream.of(), we need to use the method reference and the Supplier interface:

如果我们想懒散地评估传递给Stream.of()的方法,我们需要使用方法引用和Supplier接口。

@Test
public void givenThreeOptionals_whenChaining_thenFirstNonEmptyIsReturnedAndRestNotEvaluated() {
    Optional<String> found =
      Stream.<Supplier<Optional<String>>>of(this::getEmpty, this::getHello, this::getBye)
        .map(Supplier::get)
        .filter(Optional::isPresent)
        .map(Optional::get)
        .findFirst();

    assertEquals(getHello(), found);
}

In case we need to use methods that take arguments, we have to resort to lambda expressions:

如果我们需要使用带参数的方法,我们必须求助于lambda表达式。

@Test
public void givenTwoOptionalsReturnedByOneArgMethod_whenChaining_thenFirstNonEmptyIsReturned() {
    Optional<String> found = Stream.<Supplier<Optional<String>>>of(
      () -> createOptional("empty"),
      () -> createOptional("hello")
    )
      .map(Supplier::get)
      .filter(Optional::isPresent)
      .map(Optional::get)
      .findFirst();

    assertEquals(createOptional("hello"), found);
}

Often, we’ll want to return a default value in case all of the chained Optionals are empty. We can do so just by adding a call to orElse() or orElseGet():

通常情况下,我们希望返回一个默认值,以防所有连锁的Optionals都是空的。我们可以通过添加对orElse()orElseGet()的调用来做到这一点。

@Test
public void givenTwoEmptyOptionals_whenChaining_thenDefaultIsReturned() {
    String found = Stream.<Supplier<Optional<String>>>of(
      () -> createOptional("empty"),
      () -> createOptional("empty")
    )
      .map(Supplier::get)
      .filter(Optional::isPresent)
      .map(Optional::get)
      .findFirst()
      .orElseGet(() -> "default");

    assertEquals("default", found);
}

14. JDK 9 Optional API

14.JDK 9 Optional API

The release of Java 9 added even more new methods to the Optional API:

Java 9的发布为Optional API增加了更多新方法。

  • or() method for providing a supplier that creates an alternative Optional
  • ifPresentOrElse() method that allows executing an action if the Optional is present or another action if not
  • stream() method for converting an Optional to a Stream

Here is the complete article for further reading.

这里是完整的文章,供进一步阅读

15. Misuse of Optionals

15.滥用选项s

Finally, let’s see a tempting, however dangerous, way to use Optionals: passing an Optional parameter to a method.

最后,让我们看看使用Optionals的一个诱人的、然而危险的方法:向方法传递一个Optional参数。

Imagine we have a list of Person and we want a method to search through that list for people with a given name. Also, we would like that method to match entries with at least a certain age, if it’s specified.

想象一下,我们有一个Person的列表,我们想用一个方法在该列表中搜索具有给定名字的人。此外,我们还希望该方法能够匹配至少有一定年龄的条目,如果有指定的话。

With this parameter being optional, we come with this method:

由于这个参数是可选的,我们就用这个方法来了。

public static List<Person> search(List<Person> people, String name, Optional<Integer> age) {
    // Null checks for people and name
    return people.stream()
            .filter(p -> p.getName().equals(name))
            .filter(p -> p.getAge().get() >= age.orElse(0))
            .collect(Collectors.toList());
}

Then we release our method, and another developer tries to use it:

然后我们发布了我们的方法,而另一个开发者试图使用它。

someObject.search(people, "Peter", null);

Now the developer executes its code and gets a NullPointerException. There we are, having to null check our optional parameter, which defeats our initial purpose in wanting to avoid this kind of situation.

现在,开发人员执行其代码并得到一个NullPointerException。 在这里,我们不得不对我们的可选参数进行空检查,这违背了我们想要避免这种情况的最初目的。

Here are some possibilities we could have done to handle it better:

这里有一些我们可以做的可能性,以更好地处理它。

public static List<Person> search(List<Person> people, String name, Integer age) {
    // Null checks for people and name
    final Integer ageFilter = age != null ? age : 0;

    return people.stream()
            .filter(p -> p.getName().equals(name))
            .filter(p -> p.getAge().get() >= ageFilter)
            .collect(Collectors.toList());
}

There, the parameter’s still optional, but we handle it in only one check.

在那里,参数仍然是可选的,但我们只用一个检查来处理它。

Another possibility would have been to create two overloaded methods:

另一种可能性是创建两个重载方法

public static List<Person> search(List<Person> people, String name) {
    return doSearch(people, name, 0);
}

public static List<Person> search(List<Person> people, String name, int age) {
    return doSearch(people, name, age);
}

private static List<Person> doSearch(List<Person> people, String name, int age) {
    // Null checks for people and name
    return people.stream()
            .filter(p -> p.getName().equals(name))
            .filter(p -> p.getAge().get().intValue() >= age)
            .collect(Collectors.toList());
}

That way we offer a clear API with two methods doing different things (though they share the implementation).

这样我们就提供了一个清晰的API,有两个方法做不同的事情(尽管它们共享实现)。

So, there are solutions to avoid using Optionals as method parameters. The intent of Java when releasing Optional was to use it as a return type, thus indicating that a method could return an empty value. As a matter of fact, the practice of using Optional as a method parameter is even discouraged by some code inspectors.

所以,有一些解决方案可以避免使用Optionals作为方法参数。Java在发布Optional时的意图是将其作为一个返回类型,从而表明一个方法可以返回一个空值。事实上,使用Optional作为方法参数的做法甚至被一些代码检查员不提倡

16. Optional and Serialization

16.可选的和序列化

As discussed above, Optional is meant to be used as a return type. Trying to use it as a field type is not recommended.

如上所述,Optional是作为一个返回类型使用的。不建议尝试将其作为一个字段类型使用。

Additionally, using Optional in a serializable class will result in a NotSerializableException. Our article Java Optional as Return Type further addresses the issues with serialization.

此外,在可序列化的类中使用Optional将导致NotSerializableException我们的文章Java Optional作为返回类型进一步探讨了序列化的问题。

And, in Using Optional With Jackson, we explain what happens when Optional fields are serialized, along with a few workarounds to achieve the desired results.

而且,在使用Optional与Jackson中,我们解释了当Optional字段被序列化时会发生什么,以及一些变通方法以达到预期的结果。

17. Conclusion

17.结论

In this article, we covered most of the important features of Java 8 Optional class.

在这篇文章中,我们介绍了Java 8 Optional类的大部分重要特性。

We briefly explored some reasons why we would choose to use Optional instead of explicit null checking and input validation.

我们简单地探讨了为什么我们会选择使用Optional而不是显式空值检查和输入验证的一些原因。

We also learned how to get the value of an Optional, or a default one if empty, with the get(), orElse() and orElseGet() methods (and saw the important difference between the last two).

我们还学习了如何通过get()orElse()orElseGet()方法来获取Optional的值,或者在空的情况下获取一个默认值(并看到后两者之间的重要区别)。

Then we saw how to transform or filter our Optionals with map(), flatMap() and filter(). We discussed what a fluent API Optional offers, as it allows us to chain the different methods easily.

然后我们看到了如何通过map()、flatMap()filter()来转换或过滤我们的Optionals。我们讨论了流畅的APIOptional所提供的功能,因为它允许我们轻松地连锁不同的方法。

Finally, we saw why using Optionals as method parameters is a bad idea and how to avoid it.

最后,我们看到为什么使用Optionals作为方法参数是一个坏主意,以及如何避免它。

The source code for all examples in the article is available over on GitHub.

文章中所有例子的源代码都可以在GitHub上找到