一、什么是空指针异常?
空指针(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概述
🎈Optional
是Java 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 8
的Stream API
和Optional
类。
下面是对上述代码的解释:
- 通过
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的总结
Optional
是Java 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
类的优势会得到更加充分的发挥。