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类的优势会得到更加充分的发挥。

相关推荐
在努力的前端小白1 小时前
Spring Boot 敏感词过滤组件实现:基于DFA算法的高效敏感词检测与替换
java·数据库·spring boot·文本处理·敏感词过滤·dfa算法·组件开发
bobz9653 小时前
小语言模型是真正的未来
后端
一叶飘零_sweeeet3 小时前
从繁琐到优雅:Java Lambda 表达式全解析与实战指南
java·lambda·java8
DevYK4 小时前
企业级 Agent 开发实战(一) LangGraph 快速入门
后端·llm·agent
艾伦~耶格尔4 小时前
【集合框架LinkedList底层添加元素机制】
java·开发语言·学习·面试
一只叫煤球的猫4 小时前
🕰 一个案例带你彻底搞懂延迟双删
java·后端·面试
最初的↘那颗心4 小时前
Flink Stream API 源码走读 - print()
java·大数据·hadoop·flink·实时计算
冒泡的肥皂4 小时前
MVCC初学demo(一
数据库·后端·mysql
颜如玉5 小时前
ElasticSearch关键参数备忘
后端·elasticsearch·搜索引擎
JH30735 小时前
Maven的三种项目打包方式——pom,jar,war的区别
java·maven·jar