善用Optional,告别NPE

京东云开发者
• 阅读 55

作者:京东物流 王亚宁

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"); // 创建包含值的Optional
Optional<String> emptyOptional = Optional.empty(); // 创建空的Optional
Optional<String> nullableOptional = Optional.ofNullable(null); // 可以接受null
获取值
// 使用get()获取值,如果为空则抛出NoSuchElementException
optional.get();

// 使用orElse()提供默认值
String value = optional.orElse("Default");

// 使用orElseGet()提供默认值的Supplier
String 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()处理嵌套的Optional
Optional<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”。

点赞
收藏
评论区
推荐文章
LinMeng LinMeng
3年前
call、apply、bind三者为改变this指向的方法。
共同点:第一个参数都为改变this的指针。若第一参数为null/undefined,this默认指向window差异点如下:1.call(无数个参数)第一个参数:改变this指向第二个参数:实参使用之后会自动执行该函数functionfn(a,b,c){console.log(this,abc);//this指
Wesley13 Wesley13
3年前
java,poi读取excel单元格为空的数据,出现空指针异常:java.lang.NullPointerException
java,poi读取excel单元格为空的数据,出现空指针异常:java.lang.NullPointerException参考文章:(1)java,poi读取excel单元格为空的数据,出现空指针异常:java.lang.NullPointerException(https://www.oschina.net/action/GoToLi
京东云开发者|深入JDK中的Optional
概述:Optional最早是Google公司Guava中的概念,代表的是可选值。Optional类从Java8版本开始加入豪华套餐,主要为了解决程序中的NPE问题,从而使得更少的显式判空,防止代码污染,另一方面,也使得领域模型中所隐藏的知识,得
Easter79 Easter79
3年前
SpringBoot自定义序列化的使用方式
场景及需求:项目接入了SpringBoot开发,现在需求是服务端接口返回的字段如果为空,那么自动转为空字符串。例如:\    {        "id":1,        "name":null    },    {        "id":2,        "name":"x
Wesley13 Wesley13
3年前
Java8(5):使用 Optional 处理 null
Java8(5):使用Optional处理null写过Java程序的同学,一般都遇到过NullPointerException:)——为了不抛出这个异常,我们便会写如下的代码:UserusergetUserById(id);if(user!
Wesley13 Wesley13
3年前
Java有关于Null的九件事
为什么在Java中需要学习null?因为如果你对null不注意,Java将使你遭受空指针异常的痛苦,并且你也会得到一个沉痛的教训。精力充沛的编程是一门艺术,你的团队、客户和用户将会更加欣赏你。以我的经验来看,导致空指针异常的一个最主要的原因是对Java中null的知识还不够。你们当中的很多已经对null很熟悉了,但是对那些不是很熟悉的来说,可以学到一些关于n
Easter79 Easter79
3年前
TurnipBit开发板DIY呼吸的吃豆人教程实例
  转载请以链接形式注明文章来源(MicroPythonQQ技术交流群:157816561,公众号:MicroPython玩家汇)  0x00前言  吃豆人是耳熟能详的可爱形象,如今我们的TurnipBit也集成了这可爱的图形,我们这就让他来呼吸了~。  0x01效果展示  先一起看下最终的成品演示视频:  http:/
Stella981 Stella981
3年前
JVM 字节码指令表
字节码助记符指令含义0x00nop什么都不做0x01aconst\_null将null推送至栈顶0x02iconst\_m1将int型1推送至栈顶0x03iconst\_0将int型0推送至栈顶0x04iconst\_1将int型1推送至栈顶0x05ic
Stella981 Stella981
3年前
Javascript中的基本数据类型
Undefined在var或者let中声明了变量但没有赋值时,这个变量的值就是undefined.使用typeof关键字检测未声明变量的类型为undefined.Nullnull表示一个空对象指针,所以用typeof检测null时,会返回objectundefine派生自null,
Stella981 Stella981
3年前
SpringBoot自定义序列化的使用方式
场景及需求:项目接入了SpringBoot开发,现在需求是服务端接口返回的字段如果为空,那么自动转为空字符串。例如:\    {        "id":1,        "name":null    },    {        "id":2,        "name":"x