Java 中如何告别面条式的 null 判断
[TOC]
背景
最近在写 Java
,其中 MongoDB
中存了某个文档数据,「如果存在」结构是如下这样:
{
"localtion":{
"pickup":{
"region":{
"region_en":"China"
}
}
}
}
现在需要判断如果 region_en
有值就拿出来,在 PHP
中用 empty()
函数处理即可:
$regionEn = '';
if(!empty($carDetail['location']['pickup']['region']['rengion_en'])) {
$regionEn = $carDetail['location']['pickup']['region']['rengion_en'];
}
由于刚开始写 Java
时没有留意,写 carDetail.getLocation().getPickup().getRegion().getRegionEn()
经常会报 NullPointExceptions
(很多Java
文章会称其为NPE
).
改为:
String regionEn = "";
if(
carDetail != null
&& carDetail.getLocation() != null
&& carDetail.getLocation().getPickup() != null
&& carDetail.getLocation().getPickup().getRegion().getRegionEn() != null
) {
regionEn = carDetail.getLocation().getPickup().getRegion().getRegionEn();
}
或者有其他逻辑需要处理那么可能是此类嵌套判断 NPE
的情况:
String regionEn = "";
if(carDetail != null) {
// 其他逻辑
if(carDetail.getLocation() != null) {
// 其他逻辑
if(carDetail.getLocation().getPickup() != null) {
// 其他逻辑
regionEn = carDetail.getLocation().getPickup().getRegion().getRegionEn() != null
? carDetail.getLocation().getPickup().getRegion().getRegionEn()
: "";
}
}
}
在 Java8
中引入 java.util.Optional
工具类 可以相对简洁的处理这种「面条式“if-else if”」的 null
判断代码。
String regionEn = Optional.ofNullable(carDetail)
.map(c -> c.getLocation())
.map(l -> l.getPickup())
.map(p -> p.getRegionEn())
.orElse("else");
Optional
类源码简读
- Java 8
java.util.Optional
工具类文档 - Java 20
java.util.Optional
工具类文档 - 源码:src/java.base/share/classes/java/util/Optional.java
- Java 8新特性(三):Optional类
- Optional类与使用==判断null有什么区别?使用Optional类有什么优势?
Optional
主要是用于避免空指针异常(NullPointerException
)的一个工具类(可看做是类似一种“语法糖”),可以通过链式调用的方式进行管道处理数据。
通过 IDEA 简单的浏览一下源码的 Structure
,该类比较简单:
@jdk.internal.ValueBased
标注了这是一个value-based
类,即:基于值的类- 什么是基于值的?只要“值”相同,这两个对象就应该被视为完全相同的对象
- 注意事项是什么?不应该使用引用相等(如:
==
操作符)去判断基于值的对象是否相等,而是使用equals()
方法 - https://matthung0807.blogspot.com/2019/05/java-value-based-classes.html
- 该类有两个私有属性
- 空 Optinal 类对象:
private static final Optional<?> EMPTY = new Optional<>(null)
- 泛型 T 的 value 对象:
private final T value
- 空 Optinal 类对象:
- 该类构造方法是私有的,不能直接实例化
- 注意:
Optional
并非解决繁杂的if-else if
,而是为了避免用if-else if
的进行null
空指针异常判断而诞生的!!!
1、获取 Optional
对象
- 获取一定不为空的
Optional<T>
对象:Optional.of(T value)
,表示value
一定为非空的,如果为null
会抛出NullPointerException
异常 - 获取空的
Optional<T>
对象:Optional.empty()
,返回(Optional<T>) new Optional<>()
对象「这个空 Optional 对象的意义是什么呢??」 - 获取可能不为空的
Optional<T>
对象:Optional.ofNullable(T value)
,其实就是return value == null ? Optional.empty() : Optional.of(value)
2、过滤、转换数据的方法
if (value != null) {
// true 处理逻辑
} else {
// false 处理逻辑
}
filter(Predicate<? super T> predicate)
: 如果value!=null
或者predicate.test(value)
结果为 true 返回自身,否则返回Optional.empty
ifPresent(Consumer<? super T> consumer)
:基于值null != null
的情况执行if
逻辑ifPresentOrElse(Consumer<? super T> consumer)
: Java 9 新增方法,基于值的null
情况,处理if-else
的情况map(Function<? super T, ? extends U> mapper)
: 如果当前对象的 value 值存在则将mapper.apply()
方法的结果包装为一个Optional
对象返回,即:「Optional<Optional<U>>」 对象flatMap(Function<? super T, Optional<U>> mapper)
当value != null
时返回mapper.apply()
结果「Optional<U>」对象。否则返回Optional.empty()
or(Supplier<? extends Optional<? extends T>> supplier)
: Java 9 新增方法,如果value
为null
则返回(Optional<T>) supplier.get()
stream()
:Java 9 新增方法,返回一个Stream<T>
流对象
!!!注意:
Predicate
、Consumer
、Consumer
、Function
都是函数式接口,其中只有 1 个方法,因此可以用Lambda
表达式来匿名实现- 这些过滤、转换方法,传入参数是
@FunctionalInterface
的匿名函数式接口实现,如果传null
都会报异常 - map、flatMap 两个方法是有差异的
3、获取 Optional
的值
get()
: 获取value
值,如果value
为null
则抛出NoSuchElementException
异常,因此使用的时候需要确定对象的value
是否确实有值orElse(T other)
: 如果value
不为null
则返回value
,否则返回other
「PHP 中的三元运算符a ?: b
,若 a true 则返回 a,否则返回 b」orElseGet(Supplier<? extends T> other)
: 如果value
为null
则调用实现了Supplier Interface
的other.get()
方法返回的默认内容orElseThrow(Supplier<? extends X> exceptionSupplier)
: 如果值为null
则throw exceptionSupplier.get()
(自定义异常的 Message 信息)toString()
: 适合记录 log 的时候,返回字符串String.format("Optional[%s]", value)
或 字符串Optional.empty
hashCode()
: 返回value
值的HashCode
,如果值为null
则返回0
orElseThrow()
:Java 10
新增的方法,如果值为null
抛出 ` NoSuchElementException(“No value present”)` 异常(难道官方发现大家都抛出类似的信息,为了方便所以直接加了一个这样的方法??)
4、value
判断方法
isPresent()
: 判断value != null
则返回true
equals()
: 判断两个Optional
对象的值是否相等isEmpty()
:Java 11
添加的方法,用于判断Value
是否为null
疑问
1、为什么要有 Optional.empty()
返回一个空 Optional 对象??
看到很多示例对 Optional.empty()
的解释都是一笔带过,因为理论上我们应该不需要主动的获取一个空对象呀…很纳闷为啥要有这么一个空对象呢?什么场景会用到呢?
Optional.empty()
生成一个没有包含任何值的 Optional
对象,当期望某个方法可能不会返回结果时,你可以返回 Optional.empty()
,而不是返回 null
。这样的话,调用者可以使用 Optional
的方法(如 isPresent()
或 ifPresent()
)来处理可能的空值,而不必进行显式的 null
检查。
例如,考虑一个方法,可能找不到期望的数据并返回 null
:
public User findUserByName(String name) {
// 如果找不到用户,返回 null
}
现在改用 Optional
:
public Optional<User> findUserByName(String name) {
// 如果找不到用户,返回 Optional.empty()
}
对于调用者来说,处理方法如下:
Optional<User> optionalUser = findUserByName("test");
if (optionalUser.isPresent()) {
User user = optionalUser.get();
// 执行其他操作
} else {
// 用户不存在的情况下的处理
}
但是这种方法和直接使用 if( user != null) {} else {}
没有什么区别呀…
但如果配上 ifPresent()
更简洁的方式:
findUserByName("test").ifPresent(user -> {
// 执行其他操作
});
似乎稍微有一些看头,让代码段可以更加集中于“逻辑的提现”,而避免了很多面条式的 if-else
堆积。
但上面的代码,else
的逻辑会丢失。因此在 Java 9
中新增了 ifPresentOrElse()
方法:
findUserByName("test").ifPresentOrElse(
user -> {
// 执行存在用户时的操作
},
() -> {
// 执行用户不存在时的操作,else 的逻辑
}
);
2、map()
和 flatMap()
的区别?
由于刚开始接触 Java,对泛型了解不多,因此被我忽略的点在于:map(Function<? super T, ? extends U> mapper)
和 flatMap(Function<? super T, Optional<U>> mapper)
的参数 mapper
的类型。
两个函数的源码如下:
public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
Objects.requireNonNull(mapper);
if (!isPresent())
return empty();
else {
// 此处对 mapper.apply(value) 的结果再次进行了 Optional.ofNullable 的包装
return Optional.ofNullable(mapper.apply(value));
}
}
public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) {
Objects.requireNonNull(mapper);
if (!isPresent())
return empty();
else {
// 直接返回了 mapper.apply(value) 的结果,只要结果不是 null 即可,否则会抛 NPE
return Objects.requireNonNull(mapper.apply(value));
}
}
Function<? super T, ? extends U> mapper
表示mapper
对象必须处理 T 类型,但接口是返回 U 类型Function<? super T, Optional<U> mapper
表示mapper
对象必须处理 T 类型,但接口是返回Optional<U>
类型map()
和flatMap()
都是要接收一个「实现了Fuction
函数式接口的对象」的形参
依然通过 CarDetail
写一个 Demo
:
package optional;
import java.util.Optional;
public class DemoOptional {
public static void main(String[] args) {
CarDetail carDetail = null;
String regionEn = Optional.ofNullable(carDetail)
.flatMap(CarDetail::getLocation) // 此处用的是:flatMap(),`CarDetail.location` 是 `Optional<T>`
// .map(x -> x.orElse(new Location()).getPickup()) // 如果 flatMap() 想用 map() 代替
.map(Location::getPickup) // 此处用的是:`map()`,`Location.pickup` 是一个 `Region` 实体
.map(Region::getRegionEn)
.orElse("else");
System.out.println(regionEn);
}
public static class CarDetail {
private Optional<Location> location;
public Optional<Location> getLocation() {return location;}
public CarDetail setLocation(Location location) {this.location = Optional.ofNullable(location); return this;}
}
public static class Location {
private Region pickup;
public Region getPickup() {return pickup;}
public Location setPickup(Region pickup) {this.pickup = pickup;return this;}
}
public static class Region {
private String regionEn;
Region(String regionEn) {this.regionEn = regionEn;}
public String getRegionEn() {return regionEn;}
}
}
Optional.ofNullable(carDetail)
得到了一个Optional<CarDetail>
的对象- 由于
CarDetail.location
本身已经是一个Optional<Location>
对象 - 因此
Optional.ofNullable(carDetail).map(CarDetail::getLocation)
得到的将是一个Optional.ofNullable(CarDetail::getLocation)
的对象 - 而
Optional.ofNullable(carDetail).flapMap(CarDetail::getLocation)
得到的将是一个CarDetail::getLocation
对象
Java
是希望对开发者屏蔽 「指针」这个复杂概念的,但是偏偏返回了 null
…这是一个「空指针」,因此出现 Optional
类希望能用 Optional.empty()
来屏蔽返回真正 null
的情况。
所以当我们选择 map() or flipMap()
的时候,即:面临 「“对象”嵌套“对象”」 情况下的要看 内部嵌套的这个对象是一个 Optional<T>
还是一个其他的实体对象?