Optional 是一个容器对象:要么包含一个非 null 的值,要么为空(empty)。有值时 isPresent() 返回 true、get() 返回该值;为空时 isPresent() 返回 false。
不保存 null——容器内部的 null 只表示「没有值」(Optional.empty()ofNullable(null) 拿到的都是它);Optional 变量本身也永远不该是 null,这是官方 API Note 明确要求的。
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和一个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(),区别是值不存在时接收的不是现成的默认值,而是一个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()的区别

orElse()和orElseGet()看起来功能重叠,很多刚接触Optional或 Java 8 的人也分不清两者的区别。
两者差别很小,但用错了会明显影响性能。

  • 在测试类里加一个 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(),行为完全相同。
  • 再跑一个「值存在」的测试,这时连默认值都不应该被创建:
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()不管包装值是否存在,都会先把默认对象创建出来,值存在时这个对象就白建了。

这个例子里,创建一个默认对象成本很低。然而,当像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时返回值,否则抛 NoSuchElementException:

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
}
  • Modem 只有一个字段:价格。下面先看没有 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可以链在一起做更复杂的转换。
  • 假设要检查用户输入的密码是否正确:先用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()转换值

flatMap() 与 map() 的差别在于映射结果还要不要再包一层:map() 会把映射结果按 ofNullable() 包起来,所以映射函数本身返回 Optional 时会得到嵌套的 Optional<Optional<T>>;flatMap() 要求映射函数直接返回 Optional,也不会再包装,嵌套因此被摊平。边界也别记错:map() 的映射函数返回 null 只会得到空 Optional,flatMap() 的映射函数返回 null 直接抛 NullPointerException(上面示例里的 Person::getName 返回的就是 Optional<String>)。
前面包装的都是String和Integer这样的简单对象,实际代码里的值则常常来自复杂对象的访问器。

  • 包装一个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)

Stream API 本来是给集合用的,Java 9 给 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():配一个 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 给Optional加了stream()方法之后,一行就够了:
1
2
3
List<String> filteredList = listOfOptionals.stream()
.flatMap(Optional::stream)
.collect(Collectors.toList());

多个Stream链接Optionals

  • 想从多个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);
}
  • 用Stream API 串联几个Optional,取第一个非空值:
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方法都会被执行,不管非空的那个出现在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() 是并列的方法,区别在值缺失时的行为——它不返回默认值,而是抛异常:传异常构造器的那个重载(Java 8 起)抛自定义异常,无参版本(Java 10 起)抛 NoSuchElementException
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 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 作为方法参数传递。
假设有一个 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);

对方照着调用,传了 null,直接拿到NullPointerException。 结果还是得给这个「可选参数」做 null 检查,跟最初想避免 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());
}
  • 这样对外是两个重载,各自表达意图,实现共用一份

总结

Java 8 Optional类的重要特性基本都在上面。
用Optional而不是显式 null 检查,理由前面给过几个。
取值用get()、orElse()、orElseGet(),值为空时给默认值。
转换和过滤用 map()、flatMap()、filter(),这些方法可以直接链起来。

参考资料

系列索引:Java 系列,语言特性与运行时的长文集