Java8 Optional:空指针异常NPE的克星

一、什么是空指针异常?

空指针(Null Pointer Exception,NPE)是Java中最常见不过的异常了。其原因虽然显而易见,但是开发人员往往会忽略,或未能及时采取措施,常会导致程序崩溃或产生不可预测的行为。

二、空指针场景

防止NPE,是程序员的基本修养,注意NPE产生的场景:

返回类型为基本数据类型,return包装数据类型的对象时,自动拆箱有可能产生NPE

反例public int method() { return Integer 对象; },如果为null,自动解箱抛NPE

  • 对象为空:Object.getProperty(); 当Object为空时会抛NPE
  • 集合里的元素为空,即便我们使用CollectionUtils.isNotEmpty()判空,取出的数据元素也有可能为NULL
  • 级联调用user.getAddress().getCountry()级联调用为空时会抛空指针;
  • 常见的RPC远程调用,返回的数据对象为空;

三、Optional概述

🎈OptionalJava 8中引入的一个类,用于描述一个值不存在的情况。它可以存储任意类型的值,或者表示一个空值。使用Optional类可以避免null值的传递和检查,提高代码的健壮性和可读性。

四、Optional常用属性

基本上的使用方式都是

Optional.ofNullable(需要判断的对象).ifPresent(具体操作)

4.1 检查Optional是否有值

  • 4.1.1 empty()

返回一个空的Optional实例:Optional.empty

  • 4.1.2 of(T value)

创建一个包含非null值的Optional对象。如果值为null,则会抛出NullPointerException异常。

  • 4.1.3 ofNullable(T value)

创建一个包含指定值的Optional对象。如果该值为null,则创建一个空的Optional对象。

可以使用静态方法of()ofNullable()来创建一个非空的Optional对象,比如:

ini 复制代码
tring value = "Hello World";
Optional<String> optionalStr = Optional.of(value);
System.out.println(optionalStr); // 输出Optional[Hello World]

String nullValue = null;
Optional<String> optionalNull = Optional.of(nullValue); // 抛出NullPointerException
  • 4.1.4 get()

获取Optional对象中的值。如果Optional对象为空,则会抛出NoSuchElementException异常。

  • 4.1.5 isPresent()

判断Optional对象是否有值,返回一个boolean类型值。

vbnet 复制代码
Optional<String> optionalStr = Optional.of("Hello World");
if (optionalStr.isPresent()) {
    System.out.println("optionalStr存在值:" + optionalStr.get());
} else {
    System.out.println("optionalStr不存在值");
}
  • 4.1.6 ifPresent(Consumer<? super T> consumer)

如果Optional对象包含值,则对该值执行指定的操作。

4.2 Optional的链式调用

可以使用map()flatMap()filter()等方法进行链式调用,操作Optional的值。其中map()flatMap()可以对Optional中的值进行映射或转换操作,filter()可以进行过滤操作。

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

使用map()方法可以对Optional对象中的值进行映射或转换操作,返回一个新的Optional对象。

  • 4.2.2 flatMap(Function<? super T,Optional> mapper)

使用flatMap()方法可以对Optional对象中的值进行映射或转换操作,返回一个新的Optional对象,与map()方法不同的是,flatMap()方法返回的是一个Optional对象,而map()方法返回的是一个包含Optional对象的Optional对象。

  • 9. orElse(T other)

如果Optional对象不包含值,则返回指定的默认值。

使用orElse()方法可以设置一个默认值,如果Optional对象中的值为空,则返回默认值。

ini 复制代码
Optional<String> optionalStr = Optional.of("Hello World");
String value = optionalStr.orElse("Default Value");
System.out.println(value); // 输出Hello World

Optional<String> optionalNull = Optional.empty();
String nullValue = optionalNull.orElse("Default Value");
System.out.println(nullValue); // 输出默认值 Default Value
  • 10. orElseGet(Supplier<? extends T> other)

如果Optional对象不包含值,则返回由指定的Supplier生成的默认值。

使用orElseGet()方法可以设置一个Supplier接口实现,返回一个默认值,如果Optional对象中的值为空,则调用Supplier接口实现获取默认值,比如:

ini 复制代码
Optional<String> optionalStr = Optional.of("Hello World");
String value = optionalStr.orElseGet(() -> "Default Value");
System.out.println(value); // 输出Hello World

Optional<String> optionalNull = Optional.empty();
String nullValue = optionalNull.orElseGet(() -> "Default Value");
System.out.println(nullValue); // 输出默认值 Default Value
  • 11. orElseThrow(Supplier<? extends X> exceptionSupplier)

如果Optional对象不包含值,则抛出由指定的Supplier产生的异常。

五、Optional的优点和应用场景

  • 避免空指针异常 :使用Optional类可以避免空指针异常,提高代码的健壮性。
  • 优雅的代码处理 :使用Optional类可以使代码更加简洁和优雅,减少代码的嵌套层级,提高代码可读性。
  • 显式地指出变量可能为空 :使用Optional类可以明确地指出一个变量可能为空,从而提醒程序员对此进行处理。
  • 对集合的处理效率更高 :使用Optional类对集合进行处理,可以避免遍历整个集合,从而提高处理效率。

下面我们看几个使用Optional类过滤集合中的null值的示例:

java 复制代码
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

public class ListUtils {
    public static List<String> filterNullValues(List<String> list) {
        return list.stream()
                .filter(str -> str != null)
                .map(Optional::ofNullable)
                .filter(Optional::isPresent)
                .map(Optional::get)
                .collect(Collectors.toList());
    }
}

上述代码中,ListUtils.filterNullValues(List list)方法会过滤输入的List对象中的null值,并返回一个新的List对象。过程中使用了Java 8Stream APIOptional类。

下面是对上述代码的解释:

  • 通过list.stream()方法将输入的List对象转换成一个Stream对象。
  • 使用filter()函数过滤掉值为null的元素。
  • 使用map()函数将不为null的元素包装成Optional对象。
  • 使用filter()函数过滤掉没有值的Optional对象。
  • 使用map()函数获取Optional对象中的值。
  • 最后使用collect()函数将过滤过后的元素收集起来,并返回一个新的List对象。

✔️使用Optional类可以避免null值的传递和检查,减少代码的嵌套层级,提高代码可读性。

再比如我们在获取rpc远程调用的结果时,可以基于Optional+Map函数做数据判空校验:

c 复制代码
/**  
 * 远程调用响应处理工具类  
 */  
@Slf4j  
@Component  
public class RemoteResponseUtils {  
  
    /**  
    * 检查响应结果是否正常  
    */  
    public static boolean checkResponse(BaseResponse respBody) {  
        return ofNullable(respBody)  
            .map(BaseResponse::getCode)  
            .map(code -> {  
                if (GlobalErrorCode.OK.getCode().equals(code)) {  
                return true;  
                } else {  
                    log.warn("[rpc invoke] checkResponse respBody error:{}", respBody); 
                    return false;  
                }  
            }).orElse(false);  
    }  
  
  /**  
    * 获取响应结果,如果为空,返回默认值
    */ 
    public static <T> T getData(BaseResponse<T> baseResponse, T defaultValue) {  
        return ofNullable(baseResponse)  
            .filter(b -> {  
                if (log.isDebugEnabled()) {  
                    log.debug("[rpc invoke]-RespBody-getData: {}", b);  
                }  
                if (ResponseCodeEnum.SUCCESS.getCode() == b.getCode()) {  
                    return true;  
                } else {  
                    log.warn("[rpc invoke] getData respBody error :{}", baseResponse);  
                    return false;  
                }  
            }).map(BaseResponse::getData).orElse(defaultValue);  
    }  

    /**  
    * 获取响应结果,如果为空,返回null
    */ 
    public static <T> T getData(BaseResponse<T> baseResponse) {  
        return ofNullable(baseResponse)  
            .filter(b -> {  
                if (log.isDebugEnabled()) {  
                    og.debug("[rpc invoke] respBody-getData: {}", b);  
                }  
                if (ResponseCodeEnum.SUCCESS.getCode() == b.getCode()) {  
                    return true;  
                } else {  
                    log.warn("[rpc invoke] getData respBody error :{}", baseResponse);  
                    return false;  
                }  
            }).map(BaseResponse::getData).orElse(null);  
    }  
}

六、总结

6.1 Optional的总结

OptionalJava 8中引入的一个新特性,可以避免空指针异常,提高代码的可读性和健壮性,可以在集合处理、函数式编程、I/O操作等场景下发挥重要作用。使用Optional时需要注意值是否存在,避免出现NoSuchElementException异常。

6.2 Optional的发展趋势

随着函数式编程的流行和Java 9、Java 10的发布,Optional类也会得到更多的应用和完善。在Java9中,Optional类新增了ifPresentOrElse()方法,可以在Optional对象为空时执行一个指定的操作。在Java10中,Optional类新增了or()stream()方法、or()方法可以设置一个备选值,如果Optional对象中不存在值,则返回备选值;stream()方法可以将Optional对象转换为一个包含0或1个元素的Stream流。同时,在函数式编程中,对于空值的处理也越来越重要,Optional类的优势会得到更加充分的发挥。

相关推荐
s:1031 小时前
【框架】参考 Spring Security 安全框架设计出,轻量化高可扩展的身份认证与授权架构
java·开发语言
南山十一少4 小时前
Spring Security+JWT+Redis实现项目级前后端分离认证授权
java·spring·bootstrap
闲猫5 小时前
go orm GORM
开发语言·后端·golang
427724005 小时前
IDEA使用git不提示账号密码登录,而是输入token问题解决
java·git·intellij-idea
丁卯4046 小时前
Go语言中使用viper绑定结构体和yaml文件信息时,标签的使用
服务器·后端·golang
chengooooooo6 小时前
苍穹外卖day8 地址上传 用户下单 订单支付
java·服务器·数据库
李长渊哦6 小时前
常用的 JVM 参数:配置与优化指南
java·jvm
计算机小白一个6 小时前
蓝桥杯 Java B 组之设计 LRU 缓存
java·算法·蓝桥杯
南宫生8 小时前
力扣每日一题【算法学习day.132】
java·学习·算法·leetcode
计算机毕设定制辅导-无忧学长9 小时前
Maven 基础环境搭建与配置(一)
java·maven