我是小黑,一名在互联网“苟且”的程序员
关注同名公众号【小黑说Java】,更多干货内容抢先送达,更有不定期抽奖送书活动。
流水不争先,贵在滔滔不绝
前言
不管是一名小白程序员还是有三五年工作的CRUD BOY,或者是资深的高级Java工程师,在我们日常开发中,由于null的存在,经常会遇到NullPointerException
,而我们为了避免该异常的产生,往往需要对于使用到的对象进行一些非空判断,最开始我们都会使用if…else来进行处理,比如下面的代码:
Person person = getPersonById("123");
if (person != null) {
Address add = person.getAddress();
if (add != null) {
String city = add.getCity();
if (city != null) {
System.out.println(city);
}
}
}
这种if判断的方式导致代码的阅读性和维护性都变差,并且很容易会忘记,导致出现BUG。
Java 8中推出了Optional<T>
,专门来解决这一问题。我们先来看看官方文档的介绍。
A container object which may or may not contain a non-null value. If a value is present, isPresent() will return true and get() will return the value.
Additional methods that depend on the presence or absence of a contained value are provided, such as orElse() (return a default value if value not present) and ifPresent() (execute a block of code if the value is present).
This is a value-based class; use of identity-sensitive operations (including reference equality (==), identity hash code, or synchronization) on instances of Optional may have unpredictable results and should be avoided.
通过官方文档我们可以看出,Optional<T>
是一个容器对象,容器中存放着一个值,这个值可能是空也有可能不是空,并且还提供了一系列依赖于值是否为空的方法;Optional<T>
是一个value-based类,也可以理解为基于值的类,所以像==,hash,synchronization这些操作应该避免,因为这些操作一般都是基于对象的空间地址而并非值。
方法
接下来我们看一下Optional<T>
都提供哪些方法。
创建Optional
Optional.empty() 返回一个空的 Optional
实例。
public static<T> Optional<T> empty() {
@SuppressWarnings("unchecked")
Optional<T> t = (Optional<T>) EMPTY;
return t;
}
返回一个空的Optional
。
Optional.of(T value) 返回具有 Optional
的当前非空值的Optional
public static <T> Optional<T> of(T value) {
return new Optional<>(value);
}
private Optional(T value) {
this.value = Objects.requireNonNull(value);
}
该方法传入对象必须不能为空,如果为空则会抛出空指针异常。
Optional.ofNullable(T value) 返回一个 Optional
指定值的Optional,如果非空,则返回一个空的 Optional
。
public static <T> Optional<T> ofNullable(T value) {
return value == null ? empty() : of(value);
}
ofNullable(T value)
和 of(T value)
唯一的区别是可以传入空对象,如果为空则同等于empty()
。
其他相关方法
isPresent
如果存在值返回 true
,否则为 false
。
public boolean isPresent() {
return value != null;
}
ifPresent
如果存在值,则使用该值调用指定的消费者,否则不执行任何操作。
public void ifPresent(Consumer<? super T> consumer) {
if (value != null)
consumer.accept(value);
}
注意和isPresent()
方法区分。
orElse
如果值存在返回值,否则返回 other
。
public T orElse(T other) {
return value != null ? value : other;
}
orElseGet
返回值(如果存在),否则调用 other
并返回该调用的结果。
public T orElseGet(Supplier<? extends T> other) {
return value != null ? value : other.get();
}
orElseThrow
返回包含的值(如果存在),否则抛出由提供的exceptionSupplier
创建的异常。
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
if (value != null) {
return value;
} else {
throw exceptionSupplier.get();
}
}
map
将原Optional<T>
转换为Optional<U>
,如果值为空,则返回的也是空的Optional<U>
。
public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
Objects.requireNonNull(mapper);
if (!isPresent())
return empty();
else {
// map方法中将apply的结果包装为Optional
return Optional.ofNullable(mapper.apply(value));
}
}
比如以下代码的目的是取出pOptional中person的address,如果person
为空,则返回的的是Optional.empty()
,反之则是Optional.ofNullable(person.getAddress())
:
Optional<Person> pOptional = Optional.ofNullable(getPersonById(123));
Optional<Address> address = pOptional.map(new Function<Person, Address>() {
@Override
public Address apply(Person person) {
// 返回的是值
return person.getAddress();
}
});
以上代码可简化为:
Optional<Address> add = pOptional.map(Person::getAddress);
flatMap
从结果上看和map方法一样,都是将原Optional<T>
转换为Optional<U>
,就别在于方法参数中的mapper
,map()
方法的mapper
要求apply()
方法返回的是具体的值,而flatMap()
的mapper
参数的apply()
方法返回的就是一个Optional<U>
对象。
public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) {
Objects.requireNonNull(mapper);
if (!isPresent())
return empty();
else {
return Objects.requireNonNull(mapper.apply(value));
}
}
上述map()
中的案例使用flatMap()
实现如下:
Optional<Address> address1 = pOptional.flatMap(new Function<Person, Optional<Address>>() {
@Override
public Optional<Address> apply(Person person) {
// 需要自己包装成Optional
return Optional.of(person.getAddress());
}
});
同样可以简写为:
pOptional.flatMap(person1 -> Optional.of(person1.getAddress()));
filter
对值进行过滤,如果值存在并且在predicate
中返回为true
,返回Optional
本身,否则返回empty()
。
public Optional<T> filter(Predicate<? super T> predicate) {
Objects.requireNonNull(predicate);
if (!isPresent())
return this;
else
return predicate.test(value) ? this : empty();
}
比如以下案例:
Optional<Person> pOptional = Optional.ofNullable(getPersonById(123));
Optional<String> nameOpt = pOptional.filter(new Predicate<Person>() {
@Override
public boolean test(Person person) {
return person.getAge() > 10;
}
}).map(Person::getName);
System.out.println(nameOpt.orElse("Unknown"));
简写为:
Optional<String> nameOpt = pOptional.filter(p -> p.getAge() > 10).map(Person::getName);
System.out.println(nameOpt.orElse("Unknown"));
那么通过上面的方法你是不是想立马在代码中对于你的if…else进行改造了呢?回顾我们最开始的代码:
Person person = getPersonById("123");
if (person != null) {
Address add = person.getAddress();
if (add != null) {
String city = add.getCity();
if (city != null) {
System.out.println(city);
}
}
}
我们来用Optional
改造一下看看:
Person person = getPersonById("123");
Optional<String> cityOption = Optional.ofNullable(person)
.map(Person::getAddress)
.map(Address::getCity);
System.out.println("city is :"+cityOption.orElse("Unknown city."));
是不是感觉代码看着好像“高大上”了,但是这样是正确的吗?在这个过程中其实创建了很多的Optional对象出来,起码在空间上是更浪费的。
Optional正确的使用方式应该是什么呢?
Optional使用原则
Java语言的架构师Brian Goetz明确定义:
Optional is intended to provide a limited mechanism for library method return types where there needed to be a clear way to represent “no result," and using null for such was overwhelmingly likely to cause errors.
Optional 旨在为库方法返回类型提供一种有限的机制;
其中需要一种明确的方式来表示“无结果”;
而对这种情况使用null极有可能导致错误。
Optional的设计初衷是为了作为方法的返回值,明确表示方法没有返回结果,而不是用null来表示,避免调用方因为返回null导致的错误。
那么有了这个设计意图,那么我们结合意图来看一下使用Optional
需要遵循哪些原则。
不要过度使用Optional
有时候我们学会了一个东西之后就像立马使用,看什么代码都想套进去,有个例子说:有了一把锤子后,看啥都像钉子。
总想去敲两下。比如以下代码:
避免:
public String fetchStatus() {
String status = ... ;
return Optional.ofNullable(status).orElse("PENDING");
}
而应该:
public String fetchStatus() {
String status = ... ;
return status == null ? "PENDING" : status;
}
通过三目运算很简单的功能,非要用Optional
,不光可读性变差,还额外构建了一个Optional
对象。
不要将任何域对象声明为Optional
不要将任何方法的参数(尤其是setter)和构造方法的参数设置为Optional。
因为Optional不能被序列化,所以Optional设计时就没打算被用作对象的属性。
如果想通过Optional来保证对象的属性不被设置为null值,可以通过如下方式实现:
// PREFER
public class Customer {
private final String name; // 不能为空
private final String postcode; // 可以为空
public Cart(String name, String postcode) {
this.name = Objects.requireNonNull(name, () -> "Name cannot be null");
this.postcode = postcode;
}
public Optional<String> getPostcode() {
return Optional.ofNullable(postcode);
}
...
}
可以看到这里getPostcode()
方法返回了一个Optional
,但是不要在你的代码里将所有getter都改成这样,尤其是返回集合或数组的情况,返回空集合和数组就可以了,因为集合本身就有empty()
方法可以判断是否为空。
在方法参数中使用Optionalin是另一个常见错误。这将导致代码的复杂度变高。应该在方法内部对参数进行检查,而不是强制调用方使用Optional
。
不要使用Optional替代集合返回值
通过返回空集合的形式表示方法没有返回结果,通过Collections.emptyList()
、emptyMap()
和emptySet()
构建空返回。
Optional值的比较使用equals
因为Optional
类是一个value-based
类,它的equals
方法本身就是比较值,所以如果你需要对两个Optional
中的值进行比较,则可以直接使用equals
方法。
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof Optional)) {
return false;
}
Optional<?> other = (Optional<?>) obj;
return Objects.equals(value, other.value);
}
不要将Null值赋值为Optional变量
Optional.empty()
会初始化一个Optional
代替null
值,Optional
是一个容器,被定义为null
就没有意义。
避免以下代码:
public Optional<Cart> fetchCart() {
Optional<Cart> emptyCart = null;
...
}
而应该:
public Optional<Cart> fetchCart() {
Optional<Cart> emptyCart = Optional.empty();
...
}
在调用 get() 之前确保 Optional 具有值
如果你需要调用get()方法获取Optional
值时,一定要确保在调用之前检查过Optional
中有值;通常都会使用isPresent()-get()
搭配的方式使用,但是这种方式并不优雅;但是你如果一定要选择使用get()
则不要忘记isPresent()
。
避免以下代码:
Optional<Cart> cart = ... ; // 可能返回一个空的Optional
...
// 如果cart是空的会抛出java.util.NoSuchElementException
Cart myCart = cart.get();
而应该:
if (cart.isPresent()) {
Cart myCart = cart.get();
...
} else {
...
}
值为空时,通过 orElse() 方法返回默认对象
这种方式比isPresent()-get()
方式更加优雅。但是这里有点小问题,就是即使Optional
有值时,也会执行orElse()方法,并且需要构建一个对象,所以性能上有一点小小的损耗。
避免以下代码:
public static final String USER_STATUS = "UNKNOWN";
...
public String findUserStatus(long id) {
Optional<String> status = ... ; // 可能返回一个空的Optional
if (status.isPresent()) {
return status.get();
} else {
return USER_STATUS;
}
}
而应该:
public static final String USER_STATUS = "UNKNOWN";
public String findUserStatus(long id) {
Optional<String> status = ... ; // 可能返回一个空的Optional
return status.orElse(USER_STATUS);
}
值为空时,通过 orElseGet() 方法返回默认对象
orElseGet()
是对isPresent()-get()
的另一种优雅替代方案,同时比orElse()
来说,要更加高效,因为orElseGet()
方法的参数是Java8中的Supplier
,只有Optional中的值为空时,才会执行Supplier
的get()方法,相对`orElse()没有性能损失。
避免以下代码:
public String computeStatus() {
...
}
public String findUserStatus(long id) {
Optional<String> status = ... ;
if (status.isPresent()) {
return status.get();
} else {
return computeStatus();
}
}
同样避免:
public String computeStatus() {
...
}
public String findUserStatus(long id) {
Optional<String> status = ... ;
// computeStatus() 在status不为空时也会调用
return status.orElse(computeStatus());
}
而应该:
public String computeStatus() {
... // some code used to compute status
}
public String findUserStatus(long id) {
Optional<String> status = ... ;
// computeStatus()只有在status为空时调用
return status.orElseGet(this::computeStatus);
}
总结
乍一看Optional
好像挺简单的,但是要想正确使用并不容易,OptionalAPI主要用于返回类型,清晰表达返回值中没有结果的可能性。
- 不要过度使用
Optional
Optional
尽量作为方法的返回值使用- 获取之前先判断是否有值
- 适当使用
Optional API
一些不恰当的使用可能导致在某些情况下出现BUG,建议收藏本文,使用Optional
时进行参考。
我是小黑,一名在互联网“苟且”的程序员
关注同名公众号【小黑说Java】,更多干货内容抢先送达,更有不定期抽奖送书活动。
流水不争先,贵在滔滔不绝