写点什么

善用 Optional,告别 NPE

  • 2024-12-18
    北京
  • 本文字数:2138 字

    阅读完需:约 7 分钟

作者:京东物流 王亚宁

1、NPE 是什么?

NPE:NullPointerException(空指针异常)。可以说自 Null 的诞生以来它就让无数的程序员为之哀嚎,也是无数系统 Bug 的来源。托尼·霍尔(Tony Hoare),Null 的发明者也表示过这是他十亿美元的错误。当程序试图在空引用(null)上调用方法或访问属性时,JVM 会抛出 NPE。例如:


String str = null;int length = str.length(); // 这里会抛出NPE
复制代码

1.1、NPE 的常见原因

未初始化的对象:变量声明后未赋值,即默认为 null。


方法返回 null:方法可能返回一个对象或 null,但调用者未进行 null 检查。


集合中的 null 元素:集合操作中插入了 null,后续操作未处理。


多线程环境中的竞态条件:一个线程修改对象状态为 null,另一个线程未及时检查。

1.2、NPE 的影响

程序崩溃:未处理的 NPE 会导致程序终止,影响用户体验。


调试困难:NPE 的堆栈信息可能不直观,定位问题源头耗时。


代码质量下降:频繁的 NPE 表明代码缺乏健壮的 null 处理机制。

2、Optional 库介绍

为了应对 NPE 问题,Java 8 引入了 Optional 类,它是一个容器对象,可以包含或不包含非 null 的值。通过 Optional,开发者可以显式地表示一个值是可选的,从而强制进行 null 检查,减少 NPE 的发生。

2.1、Optional 的基本用法

创建Optional对象Optional<String> optional = Optional.of("Hello"); // 创建包含值的OptionalOptional<String> emptyOptional = Optional.empty(); // 创建空的OptionalOptional<String> nullableOptional = Optional.ofNullable(null); // 可以接受null
复制代码


获取值// 使用get()获取值,如果为空则抛出NoSuchElementExceptionoptional.get();
// 使用orElse()提供默认值String value = optional.orElse("Default");
// 使用orElseGet()提供默认值的SupplierString value = optional.orElseGet(() -> "Default");
// 使用orElseThrow()在值为空时抛出异常String value = optional.orElseThrow(() -> new IllegalArgumentException("Value is null"));
复制代码


处理值// 使用ifPresent()在值存在时执行操作optional.ifPresent(val -> System.out.println(val));
// 使用map()转换值Optional<Integer> lengthOptional = optional.map(String::length);
// 使用flatMap()处理嵌套的OptionalOptional<Optional<String>> nestedOptional = Optional.of(Optional.of("Nested"));Optional<String> flatOptional = nestedOptional.flatMap(opt -> opt);
复制代码

2.2、Optional 的优势

明确的意图:方法返回 Optional 表明返回值可能为空,增强代码的可读性。


强制 null 检查:通过 Optional 的方法链,开发者必须处理可能的空值,减少遗漏。


函数式编程支持:与 Lambda 表达式和 Stream API 无缝结合,简化代码逻辑。

3、最佳实例示例

示例背景


假设有一个用户类 User,包含一个地址类 Address,而地址类中又包含城市信息 City。在获取用户的城市名称时,存在多级空指针的风险。


public class User {    private Address address;
public Address getAddress() { return address; }}
public class Address { private City city;
public City getCity() { return city; }}
public class City { private String name;
public String getName() { return name; }}
复制代码


使用传统方式处理 NPE


在没有使用 Optional 的情况下,获取城市名称可能需要多级 null 检查:


public String getUserCityName(User user) {    if (user != null) {        Address address = user.getAddress();        if (address != null) {            City city = address.getCity();            if (city != null) {                return city.getName();            }        }    }    return "Unknown";}
复制代码


上述代码层层嵌套,逻辑复杂,且易于遗漏某一级的 null 检查。并且代码也不容易阅读


使用 Optional 简化代码


利用 Optional,可以将多级 null 检查转化为链式调用,代码更加简洁明了:


public String getUserCityName(Optional<User> userOptional) {    return userOptional            .map(User::getAddress)            .map(Address::getCity)            .map(City::getName)            .orElse("Unknown");}
复制代码


另一个实例:处理方法返回值


假设有一个方法 findUserById,可能返回一个 User 对象或 null。使用 Optional 可以优雅地处理返回值。


public Optional<User> findUserById(String userId) {    User user = userRepository.findById(userId); // 可能返回null    return Optional.ofNullable(user);}
复制代码


调用方可以这样使用:


findUserById("12345")    .map(User::getAddress)    .map(Address::getCity)    .map(City::getName)    .ifPresent(cityName -> System.out.println("User city: " + cityName));
复制代码


如果 User 不存在或其地址、城市信息为 null,上述代码不会执行 ifPresent 中的打印操作,避免了 NPE 的风险。

总结

通过合理使用 Java 8 的 Optional 类,我们开发者可以有效减少 NullPointerException 的发生,提高代码的健壮性和可维护性。然而,Optional 并非万能,需结合具体场景合理使用。掌握 Optional 的使用技巧和最佳实践,将有助于编写更安全、优雅的 Java 代码,真正做到“善用 Optional,告别 NPE”。

发布于: 刚刚阅读数: 4
用户头像

拥抱技术,与开发者携手创造未来! 2018-11-20 加入

我们将持续为人工智能、大数据、云计算、物联网等相关领域的开发者,提供技术干货、行业技术内容、技术落地实践等文章内容。京东云开发者社区官方网站【https://developer.jdcloud.com/】,欢迎大家来玩

评论

发布
暂无评论
善用Optional,告别NPE_京东科技开发者_InfoQ写作社区