1. 概述
本教程将深入探讨 Java 8 引入的 Optional
类。该类的核心目标是在类型层面提供一种表示可选值的解决方案,从而替代传统的 null
引用。
想更深入理解为什么需要关注 Optional
类?建议阅读 Oracle 官方文章。
2. 创建 Optional 对象
创建 Optional
对象有多种方式:
2.1 创建空 Optional
使用静态方法 empty()
创建空 Optional
:
@Test
public void whenCreatesEmptyOptional_thenCorrect() {
Optional<String> empty = Optional.empty();
assertFalse(empty.isPresent());
}
注意:我们使用
isPresent()
检查Optional
是否包含值。只有当Optional
包装了非null
值时才返回true
。
2.2 创建非空 Optional
使用静态方法 of()
创建:
@Test
public void givenNonNull_whenCreatesNonNullable_thenCorrect() {
String name = "baeldung";
Optional<String> opt = Optional.of(name);
assertTrue(opt.isPresent());
}
⚠️ 踩坑提醒:of()
方法参数不能为 null
,否则会抛出 NullPointerException
:
@Test(expected = NullPointerException.class)
public void givenNull_whenThrowsErrorOnCreate_thenCorrect() {
String name = null;
Optional.of(name);
}
2.3 创建可空 Optional
当可能遇到 null
值时,使用 ofNullable()
方法:
@Test
public void givenNonNull_whenCreatesNullable_thenCorrect() {
String name = "baeldung";
Optional<String> opt = Optional.ofNullable(name);
assertTrue(opt.isPresent());
}
传入 null
时不会抛异常,而是返回空 Optional
:
@Test
public void givenNull_whenCreatesNullable_thenCorrect() {
String name = null;
Optional<String> opt = Optional.ofNullable(name);
assertFalse(opt.isPresent());
}
3. 检查值存在:isPresent() 和 isEmpty()
3.1 使用 isPresent()
通过 isPresent()
检查 Optional
是否包含值:
@Test
public void givenOptional_whenIsPresentWorks_thenCorrect() {
Optional<String> opt = Optional.of("Baeldung");
assertTrue(opt.isPresent());
opt = Optional.ofNullable(null);
assertFalse(opt.isPresent());
}
当包装值非 null
时返回 true
。
3.2 使用 isEmpty()(Java 11+)
Java 11 引入了 isEmpty()
方法,功能与 isPresent()
相反:
@Test
public void givenAnEmptyOptional_thenIsEmptyBehavesAsExpected() {
Optional<String> opt = Optional.of("Baeldung");
assertFalse(opt.isEmpty());
opt = Optional.ofNullable(null);
assertTrue(opt.isEmpty());
}
4. 条件执行:ifPresent()
ifPresent()
方法允许在值存在时执行代码。传统写法:
if(name != null) {
System.out.println(name.length());
}
这种写法不仅冗长,还容易出错——谁能保证后续使用时不会忘记空检查? 这可能导致运行时 NullPointerException
。
Optional
强制显式处理可空值,促进良好编程实践。使用 ifPresent()
重构:
@Test
public void givenOptional_whenIfPresentWorks_thenCorrect() {
Optional<String> opt = Optional.of("baeldung");
opt.ifPresent(name -> System.out.println(name.length()));
}
✅ 优势:两行代码替代五行,同时完成包装和隐式验证。
5. 默认值:orElse()
orElse()
方法用于获取 Optional
中的值,接受一个默认值参数:
@Test
public void whenOrElseWorks_thenCorrect() {
String nullName = null;
String name = Optional.ofNullable(nullName).orElse("john");
assertEquals("john", name);
}
当值存在时返回包装值,否则返回默认值。
6. 默认值:orElseGet()
orElseGet()
类似 orElse()
,但接受 Supplier
函数式接口:
@Test
public void whenOrElseGetWorks_thenCorrect() {
String nullName = null;
String name = Optional.ofNullable(nullName).orElseGet(() -> "john");
assertEquals("john", name);
}
仅在值不存在时调用 Supplier
生成默认值。
7. orElse 与 orElseGet 的区别
很多初学者容易混淆这两个方法。表面功能相似,但性能差异巨大:
7.1 值不存在时行为相同
public String getMyDefault() {
System.out.println("Getting Default Value");
return "Default Value";
}
@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);
}
输出:
Getting default value...
Getting default value...
当值不存在时,两者行为一致。
7.2 值存在时性能差异
@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);
}
输出:
Using orElseGet:
Using orElse:
Getting default value...
❌ 关键区别:
orElseGet()
:值存在时不调用Supplier
orElse()
:无论值是否存在,都会创建默认对象
⚠️ 性能陷阱:当默认值创建涉及数据库查询或网络调用时,orElse()
可能造成严重性能浪费。
8. 异常处理:orElseThrow()
orElseThrow()
在值不存在时抛出异常:
@Test(expected = IllegalArgumentException.class)
public void whenOrElseThrowWorks_thenCorrect() {
String nullName = null;
String name = Optional.ofNullable(nullName).orElseThrow(
IllegalArgumentException::new);
}
Java 10 引入了无参版本,抛出 NoSuchElementException
:
@Test(expected = NoSuchElementException.class)
public void whenNoArgOrElseThrowWorks_thenCorrect() {
String nullName = null;
String name = Optional.ofNullable(nullName).orElseThrow();
}
9. 获取值:get()
get()
方法直接获取包装值:
@Test
public void givenOptional_whenGetsValue_thenCorrect() {
Optional<String> opt = Optional.of("baeldung");
String name = opt.get();
assertEquals("baeldung", name);
}
⚠️ 严重缺陷:当值为 null
时抛出 NoSuchElementException
:
@Test(expected = NoSuchElementException.class)
public void givenOptionalWithNull_whenGetThrowsException_thenCorrect() {
Optional<String> opt = Optional.ofNullable(null);
String name = opt.get();
}
❌ 不推荐使用:违背 Optional
设计初衷,未来可能被废弃。建议使用 orElse()
等安全方法。
10. 条件返回:filter()
filter()
方法对包装值进行内联测试:
@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);
}
典型应用场景:验证邮箱格式、密码强度等。
10.1 实战案例:价格范围检查
传统写法:
public boolean priceIsInRange1(Modem modem) {
boolean isInRange = false;
if (modem != null && modem.getPrice() != null
&& (modem.getPrice() >= 10
&& modem.getPrice() <= 15)) {
isInRange = true;
}
return isInRange;
}
测试用例:
@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));
}
10.2 Optional 优化版
public boolean priceIsInRange2(Modem modem2) {
return Optional.ofNullable(modem2)
.map(Modem::getPrice)
.filter(p -> p >= 10)
.filter(p -> p <= 15)
.isPresent();
}
✅ 优势:
- 自动处理
null
检查 - 代码聚焦核心逻辑(价格范围验证)
- 测试用例完全兼容传统版
11. 值转换:map()
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);
}
关键特性:
- 返回转换结果包装的
Optional
- 与
filter()
区别:filter
只检查,map
执行转换
11.1 链式操作示例
@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);
}
✅ 最佳实践:先用 map
清理数据,再用 filter
验证。
12. 值转换:flatMap()
flatMap()
与 map()
类似,但能处理嵌套 Optional
。
12.1 场景示例
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);
}
// 构造方法和setter省略
}
12.2 map vs flatMap 对比
@Test
public void givenOptional_whenFlatMapWorks_thenCorrect2() {
Person person = new Person("john", 26);
Optional<Person> personOptional = Optional.of(person);
// 使用map - 需要额外解包
Optional<Optional<String>> nameOptionalWrapper
= personOptional.map(Person::getName);
Optional<String> nameOptional
= nameOptionalWrapper.orElseThrow(IllegalArgumentException::new);
String name1 = nameOptional.orElse("");
assertEquals("john", name1);
// 使用flatMap - 自动解包
String name = personOptional
.flatMap(Person::getName)
.orElse("");
assertEquals("john", name);
}
✅ 核心差异:
map
:转换后仍包装在Optional
中flatMap
:自动解包嵌套的Optional
13. Java 8 中链式处理 Optional
当需要获取多个 Optional
中第一个非空值时,Java 8 没有直接支持,但可通过 Stream 实现:
13.1 基础实现
private Optional<String> getEmpty() {
return Optional.empty();
}
private Optional<String> getHello() {
return Optional.of("hello");
}
private Optional<String> getBye() {
return Optional.of("bye");
}
@Test
public void givenThreeOptionals_whenChaining_thenFirstNonEmptyIsReturned() {
Optional<String> found = Stream.of(getEmpty(), getHello(), getBye())
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst();
assertEquals(getHello(), found);
}
❌ 缺点:所有方法都会被执行,无论非空值出现在哪个位置。
13.2 懒加载优化
@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);
}
✅ 优势:使用 Supplier
实现懒加载,后续方法在找到非空值后不会执行。
13.3 带参数方法处理
private Optional<String> createOptional(String input) {
if (input == null || "".equals(input) || "empty".equals(input)) {
return Optional.empty();
}
return Optional.of(input);
}
@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);
}
13.4 提供默认值
@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 增强
Java 9 为 Optional
新增了三个实用方法:
or()
:提供备选Optional
的 SupplierifPresentOrElse()
:值存在时执行一个操作,否则执行另一个操作stream()
:将Optional
转换为Stream
详细用法参考 Java 9 Optional 深度解析。
15. Optional 的误用
15.1 禁止作为方法参数
tempting 但危险的做法:将 Optional
作为方法参数:
public static List<Person> search(List<Person> people, String name, Optional<Integer> age) {
// 空检查省略
return people.stream()
.filter(p -> p.getName().equals(name))
.filter(p -> p.getAge().get() >= age.orElse(0))
.collect(Collectors.toList());
}
其他开发者可能这样调用:
someObject.search(people, "Peter", null); // 抛出 NullPointerException
❌ 问题:又回到了空检查的老路,违背 Optional
设计初衷。
15.2 正确替代方案
方案一:传统参数处理
public static List<Person> search(List<Person> people, String name, Integer age) {
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());
}
方案二:方法重载
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) {
return people.stream()
.filter(p -> p.getName().equals(name))
.filter(p -> p.getAge().get().intValue() >= age)
.collect(Collectors.toList());
}
✅ 最佳实践:Optional
设计初衷是作为返回类型,明确表示方法可能返回空值。作为参数使用甚至被 SonarLint 等工具警告。
16. Optional 与序列化
16.1 禁止作为字段类型
Optional
不应作为类字段类型,原因:
- 违背设计初衷(返回类型)
- 序列化问题:
Optional
未实现Serializable
16.2 序列化异常
在可序列化类中使用 Optional
字段会导致 NotSerializableException
:
public class User implements Serializable {
private Optional<String> name; // 错误!序列化会失败
}
深入问题参考 Java Optional 作为返回类型 和 Jackson 处理 Optional。
17. 总结
本文系统介绍了 Java 8 Optional
的核心特性:
关键要点
创建方式:
empty()
:空 Optionalof()
:非空 Optional(参数不能为 null)ofNullable()
:可空 Optional
值检查:
isPresent()
:值是否存在isEmpty()
(Java 11+):是否为空
值获取:
orElse()
:提供默认值orElseGet()
:懒加载默认值orElseThrow()
:值不存在时抛异常get()
:直接获取(不推荐,易抛异常)
操作方法:
ifPresent()
:值存在时执行操作filter()
:条件过滤map()
:值转换flatMap()
:处理嵌套 Optional
链式处理:
- Java 8 通过 Stream 实现链式操作
- Java 9 新增
or()
、ifPresentOrElse()
、stream()
最佳实践
✅ 推荐做法:
- 作为方法返回类型,明确表示可能为空
- 使用
orElse()
/orElseGet()
提供默认值 - 链式调用
map()
/filter()
简化逻辑
❌ 避免做法:
- 作为方法参数
- 作为类字段
- 直接调用
get()
获取值
所有示例代码可在 GitHub 获取。