在 Java 中,NullPointerException(空指针异常)是最常见的运行时异常之一。为了优雅地处理 “空值” 场景、避免直接使用 null 带来的风险,Java 8 引入了 java.util.Optional 类 —— 它本质是一个 “容器”,可以存储一个非空值(value)或表示 “无值”(empty),通过封装空值判断逻辑,让代码更简洁、可读性更高。

先看一个传统处理空值的痛点场景,如果要获取用户的地址信息,且用户、地址可能为 null,传统写法需要层层非空判断:

User user = getUserById(1);
String city = null;
if (user != null) {
    Address address = user.getAddress();
    if (address != null) {
        city = address.getCity();
    }
}
System.out.println(city); // 可能为 null

Optional 优化后,无需手动写 if (xxx != null),通过链式调用即可处理空值:

String city = Optional.ofNullable(getUserById(1))
                      .map(User::getAddress)
                      .map(Address::getCity)
                      .orElse("默认城市");
System.out.println(city);

可见 Optional 的核心价值是:统一封装空值判断逻辑,避免嵌套判空,减少 NPE 风险

创建Optional对象
方法作用注意事项
Optional.of(T value)包装一个非空valuenull,直接抛 NullPointerException
Optional.ofNullable(T value)包装一个可能为 null的值valuenull,返回 Optional.empty()
Optional.empty()创建一个 “无值” 的 Optional 实例等价于包装了 null,但不会直接暴露 null
// 1. of:包装非空值(value 不能为 null)
Optional<String> nonNullOpt = Optional.of("Hello"); 
String nullOpt = Optional.of(null); // 直接抛 NPE

// 2. ofNullable:包装可能为 null 的值(安全)
String maybeNull = null;
Optional<String> maybeNullOpt = Optional.ofNullable(maybeNull); // 结果是 empty

// 3. empty:创建无值实例
Optional<String> emptyOpt = Optional.empty(); // 结果是 empty
获取Optional中的值

get():直接获取值(易报错空指针)

orElse(T other):null返回默认值

Optional<String> opt1 = Optional.of("Java");
String result1 = opt1.orElse("Python");

orElseGet(Supplier<? extends T> supplier):null动态生成默认值

  • 作用:与 orElse 类似,但默认值由 Supplier 函数动态生成(懒加载)。
  • 优势:仅当 Optionalempty 时,才执行 supplier 逻辑,避免资源浪费(如数据库查询、复杂计算)。

orElseThrow(Supplier<? extends X> exceptionSupplier):null抛出异常

若为 empty,抛出 supplier 生成的自定义异常,替代默认的 NoSuchElementException

Optional<User> userOpt = Optional.empty();
User user = userOpt.orElseThrow(() -> new RuntimeException("用户不存在"));
过滤Optional中的值

filter(Predicate<? super T> predicate)

class User {
    private String name;
    private int age;
    // 构造器、getter
}

// 模拟查询到一个用户(年龄 17,未成年)
User minorUser = new User("Tom", 17);
Optional<User> userOpt = Optional.ofNullable(minorUser);

// 过滤:只保留年龄 >= 18 的用户
Optional<User> adultUserOpt = userOpt.filter(user -> user.getAge() >= 18);

// 结果:adultUserOpt 是 empty(17 < 18,不满足条件)
String result = adultUserOpt.map(User::getName).orElse("无成年用户");
System.out.println(result); // 输出:无成年用户
转换Optional中的值

map(Function<? super T, ? extends U> mapper)

class Address {
    private String city;
    // 构造器、getter
}

// 模拟用户有地址,地址有城市
User user = new User("Bob", 22, new Address("北京"));
Optional<User> userOpt = Optional.of(user);

// 链式转换:User -> Address -> String(城市)
String city = userOpt.map(User::getAddress) // 先提取 Address,返回 Optional<Address>
                     .map(Address::getCity) // 再提取 City,返回 Optional<String>
                     .orElse("未知城市");
System.out.println(city); // 输出:北京
处理嵌套Optional

如果 map 函数的返回值本身就是 Optional,会导致 “嵌套 Optional”(如 Optional<Optional<String>>),此时需要用 flatMap 扁平化处理:

  • map:会保留嵌套的 Optional
  • flatMap:会将嵌套的 Optional 拆成一层(直接返回内层 Optional)。
class User {
    private Optional<Address> address; // 嵌套 Optional
    // 构造器、getter
}

// 模拟用户有地址(返回 Optional<Address>)
Address address = new Address("上海");
User user = new User(Optional.of(address));
Optional<User> userOpt = Optional.of(user);

// 若用 map:会得到 Optional<Optional<Address>>(嵌套)
Optional<Optional<Address>> nestedOpt = userOpt.map(User::getAddress);

// 用 flatMap:扁平化,得到 Optional<Address>(一层)
Optional<Address> flatOpt = userOpt.flatMap(User::getAddress);

// 继续提取城市
String city = flatOpt.map(Address::getCity).orElse("未知城市");
System.out.println(city); // 输出:上海
判断值是否存在

isPresent():判断是否有值(返回 boolean)

Optional<String> opt = Optional.of("Java");
if (opt.isPresent()) {
    System.out.println("有值:" + opt.get()); // 输出:有值:Java
}

尽量避免用 if (opt.isPresent()) { ... }(类似传统判空,未发挥 Optional 优势),优先用 ifPresent 或链式调用。

ifPresent(Consumer<? super T>)

Optional 有值,将值传入 consumer 函数执行逻辑;若为空,不做任何操作。

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

// 有值时打印,无值时不执行
opt.ifPresent(value -> System.out.println("值为:" + value)); // 输出:值为:Java

// 简化写法(方法引用)
opt.ifPresent(System.out::println); // 输出:Java

为什么用 ? super T 而不是 T

允许更广泛的Consumer类型

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

// 这些都可以正常工作:
optionalStr.ifPresent((Object obj) -> System.out.println(obj.toString()));
optionalStr.ifPresent((String str) -> System.out.println(str.toUpperCase()));
optionalStr.ifPresent((CharSequence cs) -> System.out.println(cs.length()));

// 如果定义为 Consumer<T>,上面第一个就会编译错误

PECS原则的应用

  • Producer(生产者):使用 ? extends T(如:Optional<T>get()
  • Consumer(消费者):使用 ? super T(如:ifPresent() 的参数)

ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction)(Java 9+)

作用:增强版 ifPresent:有值时执行 action,无值时执行 emptyAction

Optional<String> opt1 = Optional.of("Java");
// 有值时执行 action,无值时执行 emptyAction
opt1.ifPresentOrElse(
    value -> System.out.println("有值:" + value), // 有值逻辑
    () -> System.out.println("无值")              // 无值逻辑
); // 输出:有值:Java

Optional<String> opt2 = Optional.empty();
opt2.ifPresentOrElse(
    value -> System.out.println("有值:" + value),
    () -> System.out.println("无值")
); // 输出:无值
案例

处理数据库查询结果(可能为 null)

// 模拟从数据库查询用户(可能返回 null)
public Optional<User> getUserById(Long id) {
    User user = jdbcTemplate.queryForObject("SELECT * FROM user WHERE id = ?", new Object[]{id}, User.class);
    return Optional.ofNullable(user); // 包装查询结果
}

// 调用方法:处理空值
Optional<User> userOpt = getUserById(1L);
String userName = userOpt.map(User::getName)
                         .orElseThrow(() -> new RuntimeException("用户不存在"));

处理集合中的元素(避免遍历中 NPE)

List<User> userList = Arrays.asList(
    new User("Alice", 20),
    null, // 集合中可能有 null
    new User("Bob", 22)
);

// 遍历集合,提取非空用户的姓名(用 Optional 过滤 null)
List<String> nameList = userList.stream()
                                .map(Optional::ofNullable) // 包装每个元素(处理 null)
                                .filter(Optional::isPresent) // 过滤空值
                                .map(Optional::get) // 提取值
                                .map(User::getName) // 提取姓名
                                .collect(Collectors.toList());

System.out.println(nameList); // 输出:[Alice, Bob]

处理方法参数(避免参数为 null)

// 方法参数用 Optional 包装,明确参数可能为空
public void createOrder(Optional<String> userId, Optional<Double> amount) {
    // 必须有用户 ID,否则抛异常
    String requiredUserId = userId.orElseThrow(() -> new RuntimeException("用户 ID 不能为空"));
    // 金额默认 0.0
    double orderAmount = amount.orElse(0.0);
    
    // 后续创建订单逻辑...
}

// 调用方法
createOrder(Optional.of("1001"), Optional.of(99.9)); // 正常调用
// createOrder(Optional.empty(), Optional.of(99.9)); // 抛异常:用户 ID 不能为空
常见错误用法
  1. 不要用 Optional 包装集合 / 数组
    集合(如 List)本身可以为空(null)或为空集合(new ArrayList<>()),用 Optional<List> 会增加复杂度,推荐直接返回空集合(而非 null)。
    错误:Optional<List<User>> getUsers()
    正确:List<User> getUsers()(无数据时返回 Collections.emptyList()
  2. 不要用 Optional 作为类的成员变量 / 方法参数(特殊场景除外)
    类的成员变量用 Optional 会增加序列化 / 反序列化复杂度,且大多数场景下,用 null 配合默认值即可;方法参数仅在 “参数可选且需要明确空值逻辑” 时用,否则直接用普通类型(非空参数加 @NonNull 注解)。
  3. 不要过度依赖 isPresent() + get()
    这种写法本质和传统 if (value != null) 一致,未发挥 Optional 的链式调用优势,优先用 ifPresentmaporElse 等方法。
  4. 不要在 orElse 中写耗时逻辑
    orElse 的参数会提前执行(无论 Optional 是否有值),耗时逻辑(如数据库查询)应放在 orElseGet 中(懒加载)。
功能方法关键说明
创建实例of(T)包装非空值,null 抛 NPE
ofNullable(T)包装可能为 null 的值,安全
empty()创建无值实例
获取值get()有值返回,无值抛异常,尽量避免
orElse(T)无值返回默认值,默认值提前创建
orElseGet(Supplier)无值动态生成默认值,懒加载
orElseThrow(Supplier)无值抛自定义异常,推荐用于异常场景
过滤值filter(Predicate)满足条件保留值,否则返回 empty
转换值map(Function)转换普通值,不处理嵌套 Optional
flatMap(Function)转换 Optional 值,扁平化嵌套
判断值isPresent()返回 boolean,避免单独使用
ifPresent(Consumer)有值执行逻辑,无值不操作
ifPresentOrElse(Consumer, Runnable)有值 / 无值分别执行逻辑(Java 9+)
分类: Java-Backend 标签: Java

评论

暂无评论数据

暂无评论数据

目录