Optional 使用最佳实践

Optional 是 Java 8 引入的,用来解决"返回值可能为空"的问题。但很多人用 Optional 的方式反而让代码更复杂了。

我见过这样的代码:

// ❌ 滥用 Optional
public Optional<String> findUser(Long id) {
    return Optional.ofNullable(userRepository.findById(id));
}

// 调用时
Optional<String> user = findUser(1L);
if (user.isPresent()) {
    System.out.println(user.get());
}

这完全没发挥 Optional 的优势,反而多了不必要的包装。

今天我们就来把 Optional 的正确用法讲清楚。

一、Optional 的基本概念

1.1 为什么需要 Optional

// 传统的空指针检查
User user = userService.findById(1L);
if (user != null) {
    String name = user.getName();
    if (name != null) {
        System.out.println(name.toUpperCase());
    }
}

// 使用 Optional
Optional<User> user = userService.findById(1L);
user.map(User::getName)
    .map(String::toUpperCase)
    .ifPresent(System.out::println);

Optional 把"空值检查"变成了链式调用,代码更简洁。

1.2 创建 Optional

// 1. 空 Optional
Optional<String> empty = Optional.empty();

// 2. 非空 Optional(不能传 null)
Optional<String> present = Optional.of("hello");
// Optional.of(null) 会抛出 NullPointerException

// 3. 可空 Optional(推荐)
Optional<String> nullable = Optional.ofNullable(getValue());
// ofNullable(null) 返回 Optional.empty()

1.3 Optional 的三个状态

Optional 有三个状态:

┌─────────────────────────────────────────┐
│              Optional<T>                 │
├─────────────────────────────────────────┤
│                                          │
│   Empty    │    Present(value)    │     │
│  (无值)    │    (有值)           │     │
│                                          │
└─────────────────────────────────────────┘

isPresent()  = true 表示有值
isEmpty()    = true 表示无值(Java 11+)
get()        = 获取值(无值时抛异常)

二、正确的使用方式

2.1 map 和 flatMap:转换值

Optional<User> user = userService.findById(1L);

// map:转换值
Optional<String> name = user.map(User::getName);
// 如果 user 有值,返回 Optional.of(user.getName())
// 如果 user 为空,返回 Optional.empty()

// flatMap:处理嵌套 Optional
Optional<Optional<Address>> addressOpt = user.map(User::getAddress);
Optional<Address> address = addressOpt.orElse(Optional.empty());
// 等价于
Optional<Address> address = user.flatMap(User::getAddress);

2.2 filter:条件过滤

Optional<User> user = userService.findById(1L);

// 过滤出年龄大于 18 的用户
Optional<User> adult = user.filter(u -> u.getAge() > 18);

// 过滤出名字以 "A" 开头的用户
Optional<User> aUser = user.filter(u -> u.getName().startsWith("A"));

2.3 orElse / orElseGet / orElseThrow

Optional<String> opt = findName();

// 1. orElse:无论如何都会执行参数的值
String result1 = opt.orElse("default");  // 即使 opt 有值,也创建了 "default"

// 2. orElseGet:只在 opt 为空时才执行
String result2 = opt.orElseGet(() -> computeDefault());  // 延迟加载

// 3. orElseThrow:为空时抛出异常
String result3 = opt.orElseThrow(() -> new RuntimeException("No name found"));

// 性能区别:
// 如果 findName() 返回了值
opt.orElse("default")      // 创建了不必要的 "default" 字符串
opt.orElseGet(() -> "default")  // Lambda 不执行,更快
💡

优先使用 orElseGet 而不是 orElse,除非默认值是常量。

2.4 ifPresent 和 ifPresentOrElse

Optional<String> name = findName();

// 有值时执行
name.ifPresent(n -> System.out.println("Found: " + n));

// Java 9+:有值或无值时都执行
name.ifPresentOrElse(
    n -> System.out.println("Found: " + n),
    () -> System.out.println("Not found")
);

三、常见的反模式

3.1 ❌ 反模式 1:isPresent() + get()

// ❌ 错误用法
Optional<User> user = userService.findById(1L);
if (user.isPresent()) {
    System.out.println(user.get());
}

// 等价于
User user = userService.findById(1L);
if (user != null) {
    System.out.println(user);
}

这是把 Optional 当 nullable 用,完全没发挥它的优势。

3.2 ✅ 正确用法

// ✅ 正确用法 1:ifPresent
user.ifPresent(System.out::println);

// ✅ 正确用法 2:orElse
String name = user.map(User::getName).orElse("匿名");

// ✅ 正确用法 3:orElseThrow
String name = user.map(User::getName)
    .orElseThrow(() -> new UserNotFoundException());

// ✅ 正确用法 4:链式调用
user.map(User::getAddress)
    .flatMap(Address::getCity)
    .ifPresent(System.out::println);

3.3 ❌ 反模式 2:Optional 作为字段

// ❌ 不推荐:Optional 作为字段
public class User {
    private Optional<String> nickname;  // ❌ 不要这样做
    
    public Optional<String> getNickname() {
        return nickname;
    }
}

Optional 本身已经是一个包装,再包一层字段没有必要。直接用 nullable 更好。

3.4 ❌ 反模式 3:Optional 作为方法参数

// ❌ 不推荐:Optional 作为参数
public void process(String name, Optional<String> optional) {
    // ...
}

// 调用时
process("Alice", Optional.of("value"));  // 太繁琐
process("Alice", null);                  // 还是可能传 null

更好的做法:

// ✅ 方案 1:重载
public void process(String name) {
    process(name, null);
}
public void process(String name, String optional) {
    // ...
}

// ✅ 方案 2:使用 null 表示不需要
public void process(String name, String optional) {
    // optional == null 表示没有传
}

3.5 ❌ 反模式 4:Optional 在集合中

// ❌ 不推荐
List<Optional<String>> list = new ArrayList<>();

// 正确做法
Map<String, String> map = new HashMap<>();
// 用 Map 的 containsKey 或 getOrDefault 表示有无

四、Optional 在实际场景中的应用

4.1 作为返回类型

public class UserService {
    
    // ✅ 推荐:返回 Optional
    public Optional<User> findById(Long id) {
        return Optional.ofNullable(userRepository.findById(id));
    }
    
    // ✅ 推荐:链式调用处理结果
    public String getUserCity(Long userId) {
        return userService.findById(userId)
            .map(User::getAddress)
            .flatMap(Address::getCity)
            .map(City::getName)
            .orElse("未知");
    }
}

4.2 使用 Optional 处理嵌套结构

// 传统方式
if (user != null) {
    Address address = user.getAddress();
    if (address != null) {
        City city = address.getCity();
        if (city != null) {
            System.out.println(city.getName());
        }
    }
}

// Optional 方式
Optional.ofNullable(user)
    .map(User::getAddress)
    .flatMap(Optional::ofNullable)  // 处理可能的 null
    .map(Address::getCity)
    .map(City::getName)
    .ifPresent(System.out::println);

4.3 Optional 的组合操作

// 场景:计算用户订单的总金额
Optional<User> user = userService.findById(1L);

double totalAmount = user
    .map(User::getOrders)                    // List<Order>
    .orElse(Collections.emptyList())        // 空列表
    .stream()
    .mapToDouble(Order::getAmount)
    .sum();

// 场景:获取第一个管理员的名称
Optional<String> adminName = roles.stream()
    .filter(role -> role.isAdmin())
    .findFirst()
    .map(Role::getName);

五、Optional 与 Stream 的对比

Optional 和 Stream 有一些相似之处:

// Optional 的 map/flatMap/filter 和 Stream 类似

Optional<String> name = ...;
Optional<Integer> length = name
    .filter(n -> n.length() > 3)  // filter
    .map(String::length);          // map

// Stream 的 map/flatMap/filter
Stream<String> names = ...;
Stream<Integer> lengths = names
    .filter(n -> n.length() > 3)  // filter
    .map(String::length);          // map

区别在于:

维度OptionalStream
元素数量0 或 10 或 N
可重复使用否(用完就消费)是(可以多次遍历)
主要用途替代 null数据处理流水线

六、【直观类比】

【直观类比】

Optional 就像一个盒子:

Optional<String> opt = Optional.of("hello");


    ┌───────────┐
    │    Box    │
    │ ┌───────┐ │
    │ │"hello"│ │  ← 可能有值,可能没值
    │ └───────┘ │
    └───────────┘

无值时:
Optional<String> empty = Optional.empty();


    ┌───────────┐
    │    Box    │
    │   (空)    │
    └───────────┘

你不用检查盒子是否为空,直接告诉盒子:"如果有东西,帮我做这个处理;否则给我一个默认值。"

七、Java 9+ 的增强

7.1 isEmpty()

// Java 9+
Optional<String> opt = Optional.ofNullable(getValue());

if (opt.isEmpty()) {  // 比 !opt.isPresent() 更直观
    System.out.println("No value");
}

7.2 or()

// Java 9+:支持 Optional 链接
Optional<String> result = opt1
    .or(() -> opt2)   // opt1 为空时,返回 opt2
    .or(() -> opt3);  // opt2 也为空时,返回 opt3

7.3 stream()

// Java 9+:Optional 转 Stream
List<String> names = userService.findAll()
    .stream()
    .map(User::getNickname)
    .flatMap(opt -> opt.stream())  // Optional → Stream
    .collect(Collectors.toList());

// 等价于
List<String> names = userService.findAll()
    .stream()
    .map(User::getNickname)
    .filter(Optional::isPresent)
    .map(Optional::get)
    .collect(Collectors.toList());

八、生产避坑

8.1 ❌ 错误示范:过度使用 Optional

// ❌ 过度使用
public class User {
    private String name;
    
    public Optional<String> getName() {
        return Optional.ofNullable(name);  // 不需要包装
    }
}

// 更好的做法
public class User {
    private String name;
    
    public String getName() {
        return name;  // 直接返回,允许 null
    }
}

8.2 ❌ 错误示范:返回 Optional 的 list

// ❌ 不要这样
public Optional<List<Order>> getOrders(Long userId) {
    List<Order> orders = orderService.findByUserId(userId);
    return orders.isEmpty() ? Optional.empty() : Optional.of(orders);
}

// ✅ 应该返回空 list
public List<Order> getOrders(Long userId) {
    return orderService.findByUserId(userId);  // 返回空 list 即可
}

8.3 ❌ 错误示范:get() 前不检查

Optional<String> name = findName();
// ❌ 可能抛出 NoSuchElementException
String value = name.get();

正确做法:

String value = name.orElse("default");
String value = name.orElseGet(() -> computeDefault());
String value = name.orElseThrow(() -> new RuntimeException("No value"));

九、面试追问链

第一层:基本用法

面试官问:"Optional 怎么用?"

创建用 Optional.ofNullable();判断有无值用 isPresent()ifPresent();获取值用 get()orElse()orElseThrow();转换用 map()flatMap()filter()

第二层:最佳实践

面试官追问:"使用 Optional 有什么最佳实践?"

不要用 isPresent() + get(),应该用 ifPresent()、链式调用或 orElse();不要把 Optional 作为字段或方法参数;使用 orElseGet 而不是 orElse(除非默认值是常量)。

第三层:原理

面试官追问:"Optional 的实现原理是什么?"

Optional 本身是一个包装类,内部有一个 value 字段。有值时包装值,无值时用 EMPTY 单例。不需要额外的 JVM 支持。

【学习小结】

  • Optional 替代 null 检查,让代码更简洁
  • 正确用法:map、flatMap、filter、ifPresent、orElse
  • 避免反模式:isPresent + get、Optional 作为字段/参数
  • Optional 是包装,不是数据结构
  • Java 9+ 增强了 or()、stream() 等方法