Optional 类是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。
Optional 是个容器:它可以保存类型T的值,或者仅仅保存null。Optional提供很多有用的方法,这样我们就不用显式进行空值检> 测。
Optional 类的引入很好的解决空指针异常。

  • 用例中的Person类
1
2
3
4
5
6
7
8
public class Person {
private String name;
private int age;
private String password;
private List<String> hobbies;

// getter and setter
}

创建Optional对象

使用empty()方法创建空的Optional对象

1
2
3
4
5
@Test
public void whenCreatesEmptyOptional_thenCorrect() {
Optional<String> empty = Optional.empty();
assertFalse(empty.isPresent());
}
  • isPresent() 用来检查Optional对象中是否有值,仅当我们使用非空值创建Optional时才会存在值

of()方法创建Optional对象

  • 我们可以使用静态方法 of()创建一个Optional对象:
1
2
3
4
5
6
@Test
public void givenNonNull_whenCreatesNonNullable_thenCorrect() {
String name = "baeldung";
Optional<String> opt = Optional.of(name);
assertTrue(opt.isPresent());
}
  • 传递给of()方法的参数不能为空。否则,我们将得到一个NullPointerException:
1
2
3
4
5
@Test(expected = NullPointerException.class)
public void givenNull_whenThrowsErrorOnCreate_thenCorrect() {
String name = null;
Optional.of(name);
}

ofNullable()创建可能是空值的Optional对象

1
2
3
4
5
6
@Test
public void givenNonNull_whenCreatesNullable_thenCorrect() {
String name = "baeldung";
Optional<String> opt = Optional.ofNullable(name);
assertTrue(opt.isPresent());
}
  • 通过这样做,如果我们传入一个空引用,它不会抛出异常,而是返回一个空的Optional对象:
1
2
3
4
5
6
@Test
public void givenNull_whenCreatesNullable_thenCorrect() {
String name = null;
Optional<String> opt = Optional.ofNullable(name);
assertFalse(opt.isPresent());
}

Optional中基本函数的使用

检查Optional值是否存在

  • 使用 isPresent() 方法可以检查Optional对象中是否有值.
1
2
3
4
5
6
7
8
@Test
public void givenOptional_whenIsPresentWorks_thenCorrect() {
Optional<String> opt = Optional.of("Baeldung");
assertTrue(opt.isPresent());

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

如果包装值不为空,则此方法返回true 。

  • 我们可以使用 isEmpty 方法执行相反的操作:
1
2
3
4
5
6
7
8
@Test
public void givenAnEmptyOptional_thenIsEmptyBehavesAsExpected() {
Optional<String> opt = Optional.of("Baeldung");
assertFalse(opt.isEmpty());

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

ifPresent()条件动作

  • ifPresent ()方法使我们能够在包装值被发现为非null时运行一些代码。在Optional之前,我们会做:
1
2
3
if(name != null) {
System.out.println(name.length());
}
  • 此代码在继续对其执行某些代码之前检查 name 变量是否为null 。这种方法很冗长,而且这不是唯一的问题——它也容易出错。
  • Optional使我们能够显式地处理可为 null 的值,以此作为强制执行良好编程实践的一种方式。
1
2
3
4
5
@Test
public void givenOptional_whenIfPresentWorks_thenCorrect() {
Optional<String> opt = Optional.of("baeldung");
opt.ifPresent(name -> System.out.println(name.length()));
}
1
2
3
4
5
6
7
8
9
10
private void optionalOnlyElseMethodDemo() {
var person = new Person();
person.setName("test");

if (null != person) {
System.out.println("Hello " + person.getName());
}
Optional.ofNullable(person)
.ifPresent(value -> System.out.println("Hello " + value.getName()));
}

ifPresentOrElse()方法的使用

  • 当我们有一个Optional实例时,我们通常希望对它的基础值执行特定的操作。另一方面,如果Optional为空,我们希望记录它或通过增加一些指标来跟踪该事实。
  • ifPresentOrElse ()方法正是为这种情况而创建的。我们可以传递一个Consumer ,如果定义了 Optional 将被调用,如果Optional为空则将执行Runnable 。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Test
public void givenOptional_whenPresent_thenShouldExecuteProperCallback() {
// given
Optional<String> value = Optional.of("properValue");
AtomicInteger successCounter = new AtomicInteger(0);
AtomicInteger onEmptyOptionalCounter = new AtomicInteger(0);

// when
value.ifPresentOrElse(
v -> successCounter.incrementAndGet(),
onEmptyOptionalCounter::incrementAndGet);

// then
assertThat(successCounter.get()).isEqualTo(1);
assertThat(onEmptyOptionalCounter.get()).isEqualTo(0);
}

请注意,作为第二个参数传递的回调未执行。

  • 在Optional为空的情况下,将执行第二个回调:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Test
public void givenOptional_whenNotPresent_thenShouldExecuteProperCallback() {
// given
Optional<String> value = Optional.empty();
AtomicInteger successCounter = new AtomicInteger(0);
AtomicInteger onEmptyOptionalCounter = new AtomicInteger(0);

// when
value.ifPresentOrElse(
v -> successCounter.incrementAndGet(),
onEmptyOptionalCounter::incrementAndGet);

// then
assertThat(successCounter.get()).isEqualTo(0);
assertThat(onEmptyOptionalCounter.get()).isEqualTo(1);
}
  • Demo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
private void optionalIfElseMethodDemo() {
var person = new Person();
person.setName("test");

//if-else
if (null != person) {
System.out.println("Hello " + person.getName());
} else {
System.out.println("World");
}
Optional.ofNullable(person)
.ifPresentOrElse(value -> System.out.println("Hello " + value.getName()),
() -> System.out.println("World"));
}

orElse() 给Optional赋默认值

  • orElse ()方法用于检索包装在Optional实例中的值。它采用一个参数,作为默认值。orElse ()方法返回包装值(如果存在),否则返回其参数:
1
2
3
4
5
6
@Test
public void whenOrElseWorks_thenCorrect() {
String nullName = null;
String name = Optional.ofNullable(nullName).orElse("john");
assertEquals("john", name);
}

orElseGet() 的默认值

  • orElseGet () 方法类似于orElse()。但是,如果Optional值不存在,它不会采用返回值,而是采用supplier functional interface,该接口被调用并返回调用的值
1
2
3
4
5
6
@Test
public void whenOrElseGetWorks_thenCorrect() {
String nullName = null;
String name = Optional.ofNullable(nullName).orElseGet(() -> "john");
assertEquals("john", name);
}

对比orElse和orElseGet()的区别

对于很多刚接触Optional或 Java 8 的程序员来说,orElse()和orElseGet()之间的区别并不清楚。事实上,这两种方法给人的印象是它们在功能上相互重叠。
然而,两者之间有一个微妙但非常重要的区别,如果没有很好地理解,可能会极大地影响我们代码的性能。

  • 让我们在测试类中创建一个名为getMyDefault()的方法,它不接受任何参数并返回一个默认值:
1
2
3
4
public String getMyDefault() {
System.out.println("Getting Default Value");
return "Default Value";
}
  • 让我们看两个测试并观察它们的副作用以确定orElse() 和orElseGet()重叠的地方以及它们不同的地方:
1
2
3
4
5
6
7
8
9
10
@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);
}
  • 在上面的示例中,我们将一个空文本包装在一个Optional对象中,并尝试使用两种方法中的每一种来获取包装后的值。
1
2
Getting default value...
Getting default value...
  • 在每种情况下都会调用getMyDefault()方法。碰巧当包装值不存在时,orElse()和orElseGet()的工作方式完全相同.
  • 现在让我们运行另一个存在值的测试,理想情况下,甚至不应该创建默认值:
1
2
3
4
5
6
7
8
9
10
11
12
@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);
}

在上面的示例中,我们不再包装空值,其余代码保持不变。现在让我们看一下运行这段代码的副作用:

1
2
3
Using orElseGet:
Using orElse:
Getting default value...

当使用orElseGet()检索包装值时,甚至不会调用getMyDefault()方法,因为包含的值存在。
但是,当使用orElse()时,无论包装值是否存在,都会创建默认对象。所以在这种情况下,我们只是创建了一个从未使用过的冗余对象。

在这个简单的例子中,创建一个默认对象并没有太大的成本,因为 JVM 知道如何处理这样的事情。然而,当像getMyDefault()这样的方法必须进行 Web 服务调用甚至查询数据库时,成本就变得非常明显。

使用get()获取返回值

  • 检索包装值的最终方法是get()方法:
1
2
3
4
5
6
@Test
public void givenOptional_whenGetsValue_thenCorrect() {
Optional<String> opt = Optional.of("baeldung");
String name = opt.get();
assertEquals("baeldung", name);
}

但是,与前面三种方法不同,get()只能在包装对象不为null的情况下返回一个值;否则,它会抛出一个没有这样的元素异常:

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

Optional中高级函数的使用

filter()条件过滤

1
2
3
4
5
6
7
8
9
@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);
}
  • 例子
  • 我们从某个站点接收有关调制解调器价格的推送通知,并将其存储在对象中:
1
2
3
4
5
6
public class Modem {
private Double price;

public Modem(Double price) { this.price = price; }
// standard getters and setters
}
  • 其唯一目的是检查调制解调器价格是否在我们的预算范围内, 现在让我们看一下没有Optional的代码:
1
2
3
4
5
6
7
8
9
10
11
public boolean priceIsInRange1(Modem modem) {
boolean isInRange = false;

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

isInRange = true;
}
return isInRange;
}
  • 使用 Optional#filter()之后的变体
1
2
3
4
5
6
7
public boolean priceIsInRange2(Modem modem2) {
return Optional.ofNullable(modem2)
.map(Modem::getPrice)
.filter(p -> p >= 10)
.filter(p -> p <= 15)
.isPresent();
}

map()转换Optional

  • 通过map()方法转换Optional值:
1
2
3
4
5
6
7
8
9
10
11
@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);
}
  • 我们可以将 map和filter链接在一起以做更强大的事情。
  • 假设我们要检查用户输入的密码的正确性。我们可以使用映射转换清理密码并使用过滤器检查其正确性:
1
2
3
4
5
6
7
8
9
10
11
12
13
@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);
}

flatMap()转换值

就像map()方法一样,我们也有flatMap()方法作为转换值的替代方法。不同之处在于map仅在解包时才对值进行转换,而flatMap采用包装值并在转换之前对其进行解包。
之前,我们创建了简单的String和Integer对象来包装在Optional实例中。但是,我们经常会从复杂对象的访问者那里接收到这些对象。

  • 当我们包装一个Person对象时,它将包含嵌套的Optional实例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@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);
}

or()方法去除多重判空

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public String optionalOrMethodDemo() {
var nullPerson = null;
// 普通的方式:
if (null == nullPerson) {
Person person1 = new Person();
person1.setName("test1");
if (null == person1) {
Person person2 = new Person();
person2.setName("test2");
return person2.getName();
}
return person1.getName();
}

// 使用or的方式:
var result = Optional.ofNullable(nullPerson)
.or(() -> {
Person person1 = new Person();
person1.setName("test1");
return Optional.ofNullable(person1);
})
.or(() -> {
Person person2 = new Person();
person2.setName("test2");
return Optional.ofNullable(person2);
})
.map(Person::getName)
.orElse("name");
}

Optional-Stream() (JDK9)

Java 有一个非常流畅和优雅的Stream API,可以对集合进行操作并利用许多函数式编程概念。最新的 Java 版本在Optional类上引入了stream()方法,允许我们将Optional实例视为Stream

基础使用

  • 已定义的Optional,我们正在调用它的stream()方法。这将创建一个包含一个元素的Stream ,我们可以在其上使用Stream API中可用的所有方法:
1
2
3
4
5
6
7
8
9
10
11
@Test
public void givenOptionalOfSome_whenToStream_thenShouldTreatItAsOneElementStream() {
// given
Optional<String> value = Optional.of("a");

// when
List<String> collect = value.stream().map(String::toUpperCase).collect(Collectors.toList());

// then
assertThat(collect).hasSameElementsAs(List.of("A"));
}
  • 另一方面,如果Optional不存在,调用它的stream()方法将创建一个空的Stream:
1
2
3
4
5
6
7
8
9
10
11
12
13
@Test
public void givenOptionalOfNone_whenToStream_thenShouldTreatItAsZeroElementStream() {
// given
Optional<String> value = Optional.empty();

// when
List<String> collect = value.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());

// then
assertThat(collect).isEmpty();
}
  • 对象中的属性流化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
private void optionalStreamMethodDemo() {
var person = new Person();
person.setName("Test");
var list = Arrays.asList("eat", "sleep", "dadoudou");
person.setHobbies(list);

// 方式一:使用orElse
Optional.of(person)
.map(Person::getHobbies)
.orElse(Collections.emptyList())
.stream()
.filter(value -> value.equals("eat"))
.toList()
.forEach(System.out::println);


// 方式二:使用stream
Optional.of(person)
.map(Person::getHobbies)
.stream()
.flatMap(Collection::stream)
.filter(value -> value.equals("eat"))
.toList()
.forEach(System.out::println);
}

过滤Optional-Stream流

  • Optionals流中过滤掉非空值
1
2
List<Optional<String>> listOfOptionals = Arrays.asList(
Optional.empty(), Optional.of("foo"), Optional.empty(), Optional.of("bar"));
  • 使用 filter(), Java 8 中的一个选项是使用Optional::isPresent过滤掉值,然后使用Optional::get函数执行映射以提取值:
1
2
3
4
List<String> filteredList = listOfOptionals.stream()
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toList());
  • flatMap(), 将flatMap与 lambda 表达式一起使用,该表达式将空Optional转换为空Stream实例,并将非空Optional转换为仅包含一个元素的Stream实例:
1
2
3
4
5
6
7
8
List<String> filteredList = listOfOptionals.stream()
.flatMap(o -> o.isPresent() ? Stream.of(o.get()):Stream.empty())
.collect(Collectors.toList());

// or
List<String> filteredList = listOfOptionals.stream()
.flatMap(o -> o.map(Stream::of).orElseGet(Stream::empty))
.collect(Collectors.toList());
  • Optional::stream, 随着 Java 9 的到来,将stream()方法添加到Optional中,所有这些都将变得非常简单
1
2
3
List<String> filteredList = listOfOptionals.stream()
.flatMap(Optional::stream)
.collect(Collectors.toList());

多个Stream链接Optionals

  • 我们可能需要从多个Optional中获取第一个非空Optional对象。在这种情况下,使用像orElseOptional()这样的方法会非常方便。不幸的是,Java 8 不直接支持这样的操作。
  • 让我们首先介绍一些我们将在本节中使用的方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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);
}
  • 为了链接几个Optional对象并获得 Java 8 中的第一个非空对象,我们可以使用Stream API:
1
2
3
4
5
6
7
8
9
@Test
public void givenThreeOptionals_whenChaining_thenFirstNonEmptyIsReturned() {
Optional<String> found = Stream.of(getEmpty(), getHello(), getBye())
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst();

assertEquals(getHello(), found);
}
  • 这种方法的缺点是我们所有的get方法总是被执行,无论非空Optional出现在Stream中的什么地方。
  • 如果我们想延迟评估传递给Stream.of()的方法,我们需要使用方法引用和Supplier接口:
1
2
3
4
5
6
7
8
9
10
11
@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);
}
  • 如果我们需要使用带参数的方法,我们必须求助于 lambda 表达式:
1
2
3
4
5
6
7
8
9
10
11
12
13
@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);
}
  • 通常,我们希望返回一个默认值,以防所有链接的Optional都为空。我们只需添加对orElse()或orElseGet()的调用即可:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@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);
}

异常处理

orElseThrow()异常

  • orElseThrow() 方法继承自 orElse() 和 orElseGet() 并添加了一种处理缺失值的新方法。当包装值不存在时,它不会返回默认值,而是抛出异常:
1
2
3
4
5
6
@Test(expected = IllegalArgumentException.class)
public void whenOrElseThrowWorks_thenCorrect() {
String nullName = null;
String name = Optional.ofNullable(nullName).orElseThrow(
IllegalArgumentException::new);
}

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

  • Java 10 引入了一个简化的无参数版本的orElseThrow()方法。如果是空Optional,它会抛出NoSuchElementException:
1
2
3
4
5
@Test(expected = NoSuchElementException.class)
public void whenNoArgOrElseThrowWorks_thenCorrect() {
String nullName = null;
String name = Optional.ofNullable(nullName).orElseThrow();
}

滥用Optional

最后,让我们看看使用Optional的一种诱人但危险的方法:将 Optional参数传递给方法。
假设我们有一个 Person列表,我们想要一个方法来搜索该列表中具有给定名称的人。此外,我们希望该方法匹配至少具有特定年龄的条目(如果已指定)。
由于此参数是可选的,我们使用此方法:

1
2
3
4
5
6
7
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());
}

然后我们把这个方法给另一个开发者尝试使用它:

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

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

  • 以下是我们本可以更好地处理它的一些可能性:
1
2
3
4
5
6
7
8
9
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());
}
  • 参数仍然是可选的,但我们只在一次检查中处理它

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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());
}
  • 通过这种方式,我们提供了一个清晰的 API,其中有两种方法可以做不同的事情(尽管它们共享实现)

总结

在本文中,我们介绍了 Java 8 Optional类的大部分重要特性。
我们简要探讨了为什么我们会选择使用Optional而不是显式 null 检查和输入验证的一些原因。
我们还学习了如何使用get()、orElse()和orElseGet()方法获取Optional的值,或者如果为空则获取默认值
然后我们看到了如何使用 map()、flatMap()和 filter()转换或过滤我们的Optional。我们讨论了流畅的API Optional提供了什么,因为它允许我们轻松地链接不同的方法。

参考资料