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 新增了三个实用方法:

  1. or():提供备选 Optional 的 Supplier
  2. ifPresentOrElse():值存在时执行一个操作,否则执行另一个操作
  3. 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 的核心特性:

关键要点

  1. 创建方式

    • empty():空 Optional
    • of():非空 Optional(参数不能为 null)
    • ofNullable():可空 Optional
  2. 值检查

    • isPresent():值是否存在
    • isEmpty()(Java 11+):是否为空
  3. 值获取

    • orElse():提供默认值
    • orElseGet():懒加载默认值
    • orElseThrow():值不存在时抛异常
    • get():直接获取(不推荐,易抛异常)
  4. 操作方法

    • ifPresent():值存在时执行操作
    • filter():条件过滤
    • map():值转换
    • flatMap():处理嵌套 Optional
  5. 链式处理

    • Java 8 通过 Stream 实现链式操作
    • Java 9 新增 or()ifPresentOrElse()stream()

最佳实践

推荐做法

  • 作为方法返回类型,明确表示可能为空
  • 使用 orElse()/orElseGet() 提供默认值
  • 链式调用 map()/filter() 简化逻辑

避免做法

  • 作为方法参数
  • 作为类字段
  • 直接调用 get() 获取值

所有示例代码可在 GitHub 获取。


原始标题:Guide To Java 8 Optional

« 上一篇: AspectJ 入门指南