是时候优雅的告别NullPointException了

文章介绍了Java中的Optional类,作为处理null的一种方式,以避免NullPointerException。通过创建Optional对象、使用其方法如isPresent、ifPresent、orElse等,可以更安全地处理可能为null的对象。文章还列举了Optional在不同场景下的应用,如减少判空操作、提供默认值、包装数据实体中的可选字段等,强调了其在代码中的重要性和实用性。
摘要由CSDN通过智能技术生成

NullPointException应该算是每一个程序员都很熟悉的?谁的代码不曾抛过几个空指针异常呢...

出现了NullPointException异常,虽然异常很常见,但有时候含义不明确,也会很头疼,那么如何用Optional对null处理

全面认识下Optional 


创建Optional对象 


Optional<T>对象,可以用来表示一个T类型对象的封装,或者也可以表示不是任何对象。Optional类提供了几个静态方法供对象的构建: 

方法名   功能含义描述
empty()构造一个无任何实际对象值的空Optional对象(可以理解为业务层面的null
of(T t)根据给定的对象,构造一个此对象的封装Optional对象,注意入参t不能为null,否则会空指针
ofNullable(T t)根据传入的入参t的值构造Optional封装对象,如果传入的t为null,则等同于调用empty()方法,如果t不为null,则等同于调用of(T t)方法

 在项目中,我们可以选择使用上面的方法,实现Optional对象的封装:

public void testCreateOptional() {
    // 使用Optional.of构造出具体对象的封装Optional对象
    System.out.println(Optional.of(new Content("123","QingShanYuan")));
    // 使用Optional.empty构造一个不代表任何对象的空Optional值
    System.out.println(Optional.empty());
    System.out.println(Optional.ofNullable(null));
    System.out.println(Optional.ofNullable(new Content("222","QingShanYuanAnd")));
}

输出结果: 

Optional[Content{id='111', value='JiaGouWuDao'}]
Optional.empty
Optional.empty
Optional[Content{id='222', value='JiaGouWuDao22'}]

这里需要注意下of方法如果传入null会抛空指针异常,所以比较建议大家使用ofNullable方法,可以省去调用前的额外判空操作,也可以避免无意中触发空指针问题: 

    /**
     * Returns an {@code Optional} describing the given value, if
     * non-{@code null}, otherwise returns an empty {@code Optional}.
     *
     * @param value the possibly-{@code null} value to describe
     * @param <T> the type of the value
     * @return an {@code Optional} with a present value if the specified value
     *         is non-{@code null}, otherwise an empty {@code Optional}
     */
    public static <T> Optional<T> ofNullable(T value) {
        return value == null ? empty() : of(value);
    }

 Optional常用方法理解


方法名含义说明
isPresent如果Optional实际有具体对象值,则返回true,否则返回false。
ifPresent这是一个函数式编程风格的API接口,入参是一个函数,即如果Optional对象有实际对象值,则会执行传入的入参函数逻辑,如果不存在实际对象值,则不会执行传入的入参函数逻辑。
get返回Optional封装的实际对象T数据,注意,如果实际对象数据不存在,会抛异常而非返回null
orElseget方法类似,都是获取Optional实际的对象值,区别在于orElse必须传入一个默认值,当Optional没有实际值的时候返回默认值而非抛异常
orElseGet可以理解为orElse方法的升级版,区别在于orElse仅允许传入一个固定的默认值,而orElseGet的入参是一个函数方法,当Optional无实际值时,会执行给定的入参函数,返回动态值
orElseThroworElse类似,区别在于如果没有获取到,会抛出一个指定的异常
filter判定当前Optional的实际对象是否符合入参函数的过滤规则,如果符合则返回当前Optional对象,如果不符合则返回空Optional
map接收一个入参函数,允许将Optional中的实际对象值处理转换为另一实际对象值(这个入参函数的返回值为T),并生成返回此新类型的Optional对象,如果生成的新对象为null,则返回一个空Optional对象
flatMapmap类似,区别点在于入参函数的返回值类型有区别(此处入参函数的返回值为Optional<T>

 看到这里的mapflatMap方法,肯定会联想到Stream流(文章:stream流的高级玩法

 实际使用的时候,可以根据需要选择使用map或者flatMap

public void testMapAndFlatMap() {
    Optional<User> userOptional = getUser();
    Optional<Employee> employeeOptional = userOptional.map(user -> {
        Employee employee = new Employee();
        employee.setEmployeeName(user.getUserName());
        // map与flatMap的区别点:此处return的是具体对象类型
        return employee;
    });
    System.out.println(employeeOptional);

    Optional<Employee> employeeOptional2 = userOptional.flatMap(user -> {
        Employee employee = new Employee();
        employee.setEmployeeName(user.getUserName());
        // map与flatMap的区别点:此处return的是具体对象的Optional封装类型
        return Optional.of(employee);
    });
    System.out.println(employeeOptional2);
}

注意: mapflatMap和stream中的方法类似,在stream中flatMap返回的还是个stream对象但map返回的是具体对象

 从输出结果可以看出,两种不同的写法,实现是相同的效果:

Optional[Employee(employeeName=QingShanYuan)]
Optional[Employee(employeeName=QingShanYuan)]

Optional使用场景 


 减少繁琐的判空操作

如果我们代码不进行判空处理很容易就会出现空指针异常,那我们应该怎么解决呢 

public void getCompanyFromEmployeeTest() {
    Employee employeeDetail = getEmployee();
    String companyName = Optional.ofNullable(employeeDetail)
            .map(employee -> employee.getTeam())
            .map(team -> team.getDepartment())
            .map(department -> department.getCompany())
            .map(company -> company.getCompanyName())
            .orElse("No Company");
    System.out.println(companyName);
}

 先通过map的方式一层一层的去进行类型转换,最后使用orElse去获取Optional中最终处理后的值,并给定了数据缺失场景的默认值。是不是看着比一堆if判空操作要舒服多了?

📢 适用场景: 需要通过某个比较长的调用链路一层一层去调用获取某个值的时候,使用上述方法,可以避免空指针以及减少冗长的判断逻辑。 

 需要有值兜底的数据获取场景


 编码的时候,经常会遇到一些数据获取的场景,需要先通过一些处理逻辑尝试获取一个数据,如果没有获取到需要的数据,还需要返回一个默认值,或者是执行另一处理逻辑继续尝试获取

比如从请求头中获取客户端IP的逻辑,按照常规逻辑,代码会写成下面这样: 

public String getClientIp(HttpServletRequest request) {
    String clientIp = request.getHeader("X-Forwarded-For");
    if (!StringUtils.isEmpty(clientIp)) {
        return clientIp;
    }
    clientIp = request.getHeader("X-Real-IP");
    return clientIp;
}

但是借助Optional来实现,可以这样写: 

public String getClientIp2(HttpServletRequest request) {
    String clientIp = request.getHeader("X-Forwarded-For");
    return Optional.ofNullable(clientIp).orElseGet(() -> request.getHeader("X-Real-IP"));
}

 📢 适用场景: 优先执行某个操作尝试获取数据,如果没获取到则去执行另一逻辑获取,或者返回默认值的场景。

 替代可能为null的方法返回值


 下面是一段代码片段:

public FileInfo queryOssFileInfo(String fileId) {
    FileEntity entity = fileRepository.findByIdAndStatus(fileId, 0);
    if (entity != null) {
        return new FileInfo(entity.getName(), entity.getFilePath(), false);
    }
    FileHistoryEntity hisEntity = fileHisRepository.findByIdAndStatus(fileId, 0);
    if (hisEntity != null) {
        return new FileInfo(hisEntity.getName(), hisEntity.getFilePath(), true);
    }
    return null;
}

 可以看到最终的return分支中,有一种可能会返回null,这个方法作为项目中被高频调用的一个方法,意味着所有的调用端都必须要做判空保护。可以使用Optional进行优化处理:

public Optional<FileInfo> queryOssFileInfo(String fileId) {
    FileEntity entity = fileRepository.findByIdAndStatus(fileId, 0);
    if (entity != null) {
        return Optional.ofNullable(new FileInfo(entity.getName(), entity.getFilePath(), false));
    }
    FileHistoryEntity hisEntity = fileHisRepository.findByIdAndStatus(fileId, 0);
    if (hisEntity != null) {
        return Optional.ofNullable(new FileInfo(hisEntity.getName(), hisEntity.getFilePath(), true));
    }
    return Optional.empty();
}

这样的话,就可以有效的防止调用端踩雷啦~ 

📢 适用场景: 实现某个方法的时候,如果方法的返回值可能会为null,则考虑将方法的返回值改为Optional类型,原先返回null的场景,使用Optional.empty()替代。 

 包装数据实体中非必须字段


 首先明确一下,Optional的意思是可选的,也即用于标识下某个属性可有可无的特性。啥叫可有可无?看下面代码:

public class PostDetail {
    private String title;
    private User postUser;
    private String content;
    private Optional<Date> lastModifyTime = Optional.empty();
    private Optional<Attachment> attachment = Optional.empty();
}

上面是一个帖子详情数据类,对于一个论坛帖子数据而言,帖子的标题、内容、发帖人这些都是属于必须的字段,而帖子的修改时间、帖子的附件其实是属于可选字段(因为不是所有的帖子都会被修改、也不是所有帖子都会带附件),所以针对这种可有可无的字段,就可以声明定义的时候使用Optional进行封装。

使用Optional进行封装之后有两个明显的优势:

  • 强烈的业务属性说明,明确的让人知晓这个是一个可选字段,等同于数据库建表语句里面设置nullable标识一样的效果;
  • 调用端使用的时候也省去了判空操作。

📢 适用场景: 数据实体定义的时候,对于可选参数,采用Optional封装类型替代。 

 使用抛异常替代return null


 相比于返回一个Optional封装的对象,直接抛异常具有强烈的警醒意味,意在表达此处存在预期之外的不合理情况,要求编码的时候,调用端必须要予以专门处理

public Team getTeamInfo() throws TestException {
    Employee employee = getEmployee();
    Team team = employee.getTeam();
    if (team == null) {
        throw new TestException("team is missing");
    }
    return team;
}

相比直接return null,显然抛异常的含义更加明确。 

总结 

 优雅的处理NullPointException,对空指针说再见

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

青山猿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值