Optional 使用最佳实践

面试官问:"Optional 是什么?"

候选人小严答:"Optional 是用来避免空指针异常的。"

面试官追问:"那怎么正确使用 Optional?"

小严说:"用 ofNullable() 创建,调用 orElse() 取值。"

面试官追问:"能用 Optional 做属性吗?"

小严说:"...应该可以吧?"

面试官又问:"Optional 能序列化吗?"

小严答不上来。

【面试官心理】 这道题考查的是候选人对 Optional 设计意图的理解。能说出 Optional 不适合做字段、不能序列化、并给出正确 null 检查链式用法的候选人,说明有函数式编程经验。

一、Optional 基础 🔴

1.1 创建 Optional

// of():创建非空 Optional,传入 null 抛 NPE
Optional<String> opt1 = Optional.of("hello");

// ofNullable():可能为空时使用
Optional<String> opt2 = Optional.ofNullable(maybeNull);

// empty():创建空 Optional
Optional<String> opt3 = Optional.empty();

1.2 常用方法

Optional<String> opt = Optional.ofNullable(getName());

// 判断是否有值
opt.isPresent(); // boolean

// 取值
opt.get(); // 值不存在时抛 NoSuchElementException

// 取值或默认值
opt.orElse("default"); // 值不存在返回 "default"

// 取值或自定义默认值
opt.orElseGet(() -> "computed default"); // 延迟计算

// 取值或抛异常
opt.orElseThrow(() -> new RuntimeException("Not found"));

// ifPresent:有值时执行
opt.ifPresent(name -> System.out.println(name));

// map:转换值
opt.map(String::toUpperCase);

// flatMap:处理嵌套 Optional
opt.flatMap(s -> findByName(s));

二、Optional 的三大陷阱 🔴

2.1 陷阱一:Optional 不能做字段

// ❌ 错误:Optional 作为字段
class User {
    private Optional<String> name; // 不推荐

    public Optional<String> getName() {
        return name;
    }
}

// 问题:
// 1. Optional 不能序列化!
// 2. 数据库 ORM 映射困难
// 3. 增加了不必要的复杂性

// ✅ 正确:用 null 表示缺失值
class User {
    private String name; // null 表示未设置

    public String getName() {
        return name; // 返回 null,让调用方决定如何处理
    }
}

2.2 陷阱二:orElse() vs orElseGet()

Optional<String> opt = Optional.empty();

// ❌ orElse():无论是否有值,都会执行
String s1 = opt.orElse(getDefault()); // 调用了 getDefault()!

// ✅ orElseGet():有值时不执行
String s2 = opt.orElseGet(() -> getDefault()); // 不调用

// ✅ 正确用法
String defaultName = "default";
String s3 = opt.orElse(defaultName); // 常量用 orElse
String s4 = opt.orElseGet(() -> computeDefault()); // 计算值用 orElseGet

2.3 陷阱三:Optional.of() 传入 null

// ❌ 错误
Optional<String> opt = Optional.of(null); // 抛 NullPointerException!

// ✅ 正确
Optional<String> opt = Optional.ofNullable(null); // 返回 Optional.empty()

三、链式调用最佳实践 🔴

3.1 嵌套 null 检查

// ❌ 传统写法:多层嵌套
User user = getUser();
if (user != null) {
    Address address = user.getAddress();
    if (address != null) {
        String city = address.getCity();
        if (city != null) {
            return city;
        }
    }
}
return "Unknown";

// ✅ Optional 链式写法
return Optional.ofNullable(getUser())
    .map(User::getAddress)
    .map(Address::getCity)
    .orElse("Unknown");

3.2 findFirst/findAny 的结果处理

// ❌ 错误:直接 get() 可能抛异常
String name = list.stream()
    .filter(u -> "Alice".equals(u.getName()))
    .findFirst()
    .get(); // 如果不存在,抛 NoSuchElementException

// ✅ 正确:用 orElse 或 orElseThrow
String name = list.stream()
    .filter(u -> "Alice".equals(u.getName()))
    .findFirst()
    .orElse("Unknown"); // 不存在返回 "Unknown"

3.3 filter 与 map 的组合

// 只在年龄大于等于 18 时才返回名字
Optional<String> name = users.stream()
    .filter(u -> u.getAge() >= 18)
    .map(User::getName)
    .findFirst();

// 如果没有 18+ 的用户,返回空 Optional

四、生产中的使用规范 🟡

4.1 方法返回类型

// ✅ 正确:可能返回 null 的方法用 Optional
public Optional<User> findById(long id) {
    return users.stream()
        .filter(u -> u.getId() == id)
        .findFirst();
}

// ❌ 不推荐:Optional 做字段
class User {
    private Optional<String> email; // 不要这样
}

// ✅ 推荐:字段用 null
class User {
    private String email; // null 表示未设置
    public Optional<String> getEmail() {
        return Optional.ofNullable(email);
    }
}

4.2 集合返回 Optional

// ❌ 不好:返回 Optional<List>
Optional<List<String>> findTags(long userId);

// ✅ 好:返回空列表
List<String> findTags(long userId) {
    return tags.getOrDefault(userId, Collections.emptyList());
}

五、Optional 与 Stream 的对比 🟡

场景OptionalStream
值缺失单个值可能缺失多个值
链式操作简单转换丰富操作
forEachifPresentforEach
过滤filterfilter
映射map/flatMapmap/flatMap
// Optional:适合处理单个值
Optional.ofNullable(user)
    .map(User::getName)
    .ifPresent(System.out::println);

// Stream:适合处理集合
users.stream()
    .map(User::getName)
    .forEach(System.out::println);

六、追问升级

面试官:"Optional 的内部实现是什么?"

public final class Optional<T> {
    // 两个子类:
    // 1. Optional.Empty:表示空值
    // 2. Optional.Jdk8OptionalImpl:包装非空值

    private final T value;

    // of() 创建时 value != null
    // empty() 返回空实例
    // ofNullable() 根据 value 是否为 null 决定
}

【面试官心理】 能说出 Optional 有两个内部子类的候选人,说明看过 JDK 源码。这是 P6 的加分点。