对于Java程序猿来说,NullPointerException肯定不会陌生,因为我们实在太讨厌他了,他可能是无处不在的。
String upperCaseName= user.getAddress()
.getCountry()
.getCountryName()
.toUpperCase();
当我们见到上面的代码,对于经验丰富的程序员来说肯定一眼就能看出这其中潜在空指针异常,如果想要解决就要
if (user != null) {
Address address = user.getAddress();
if (address != null) {
Country country = address.getCountry();
if (country != null) {
String name= country.getCountryName();
if (name!= null) {
String upperCaseName= name.toUpperCase();
}
}
}
}
这样一来解决了问题,但是代码的可读性就变得很差。Java8为我们引进了Optional
类。我们可以看一下Optional
的写法:
Optional.ofNullable(user)
.map(User::getAddress)
.map(Address::getCountry)
.map(Country::getCountryName)
.map(String::toUpperCase)
.orElse(null);
可以看出来这种写法相当优雅漂亮可读性也很好。
什么是Optional
Optional实际上是个容器:它可以保存类型T的值,或者仅仅保存null。Optional提供很多有用的方法,这样我们就不用显式进行空值检测。
变量存在时,Optional
类只是对类简单封装。变量不存在时,缺失的值会被建模成一个“空” 的Optional
对象,由方法Optional.empty()
返回。Optional.empty()
方法是一个静态工厂方法,它返回Optional
类的特定单一实例。你可能还有疑惑,null引用和Optional.empty()
有什么本质的区别吗?从语义上,你可以把它们当作一回事儿,但是实际中它们之间的差别非常大: 如果你尝试解引用一个null ,一定会触发NullPointerException
,不过使用 Optional.empty()
就完全没事儿,它是类的一个有效对象,多种场景都能调用,非常有用。
创建Optional对象
- 声明一个空的
Optional
通过静态工厂方法Optional.empty
,创建一个空的Optional
对象:
Optional<Car> optCar = Optional.empty();
- 依据一个非空值创建
Optional
你还可以使用静态工厂方法Optional.of
,依据一个非空值创建一个Optional
对象:
Optional<Car> optCar = Optional.of(car);
如果car
是一个null
,这段代码会立即抛出一个NullPointerException
,而不是等到你试图访问car
的属性值时才返回一个错误。
- 可接受
null
的Optional
最后,使用静态工厂方法Optional.ofNullable
,你可以创建一个允许null值的Optional对象:
Optional<Car> optCar = Optional.ofNullable(car);
如果car
是null
,那么得到的Optional
对象就是个空对象。
API
isPresent():
public boolean isPresent() {
return value != null;
}
如果值存在,返回 true
,否则返回false
,和get()
搭配使用
map(Function):
如果实例非空,调用 Function
Lambda 表达式。如果Optional
包含一个值,那函数就将该值作为参数传递给map
,对该值进行转换。如果Optional
为空,就什么也不做。
public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
Objects.requireNonNull(mapper);
if (!isPresent())
return empty();
else {
return Optional.ofNullable(mapper.apply(value));
}
}
这里我们可以注意,如果mapper.apply()
的结果是null
那么会返回empty()
,也就是Optional
包装的空对象不用担心map吐出去一个空指针!如果有orElse()
之类的直接会进入orElse()
。
flatMap(Function):
如果实例非空,调用 Function Lambda 表达式
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
有点像,那么他们区别是什么?
Map() | flatMap() |
---|---|
map方法的mapping函数返回值可以是任何类型T map中获取的返回值自动被Optional包装 | flatMap方法的mapping函数必须是Optional latMap中返回值保持不变,但必须是Optional类型 |
Optional.ofNullable(mapper.apply(value)) | Objects.requireNonNull(mapper.apply(value)) |
我们可以思考一下流当中的flatMap
是不是也是这样类似呢。
filter(Predicate):
过滤方法,相当于if操作,满足条件往下执行不满足返回空Optional
即跳到诸如orElse
的操作中
public Optional<T> filter(Predicate<? super T> predicate) {
Objects.requireNonNull(predicate);
if (!isPresent())
return this;
else
return predicate.test(value) ? this : empty();
}
举个例子:
List<User> users = Lists.newArrayList();
String filter = Optional.ofNullable(users)
.filter(CollectionUtils::isNotEmpty)
.map(users1 -> {
System.out.println("进入map");
return "map";
})
.orElse("进入orelse");
//等同于
String filterEqual = Optional.ofNullable(users)
.map(users1 -> {
if (CollectionUtils.isEmpty(users1)) {
return null;
}
System.out.println("进入map");
return "map";
})
.orElse("进入orElse");
读取Optional实例中的变量值
get()
get()
最简单但又最不安全的方法。如果变量存在,它直接返回封装的变量值,否则就抛出一个NoSuchElementException
异常。 所以,除非你非常确定Optional 变量一定包含值,否则使用这个方法是个相当糟糕的主意。此外,这种方式即便相对于嵌套式的null检查,也并未体现出多大的改进。
public T get() {
if (value == null) {
throw new NoSuchElementException("No value present");
}
return value;
}
orElse():
如果Optional
对象保存的值不是null,则返回原来的值,否则返回value。它允许你在Optional
对象不包含值时提供一个默认值。
public T orElse(T other) {
return value != null ? value : other;
}
orElseGet():
是orElse
方法的延迟调用版,Supplier
方法只有在Optional
对象不含值时才执行调用。如果创建默认值是件耗时费力的工作, 你应该考虑采用这种方式(借此提升程序的性能),或者你需要非常确定某个方法仅在Optional
为空时才进行调用,也可以考虑该方式(这种情况有严格的限制条件)。
orElseThrow():
和get
方法非常类似, 它们遭遇Optional
对象为空时都会抛出一个异常,但是使用orElseThrow
你可以定制希望抛出的异常类型。
ifPresent(Consumer)
变量值存在时执行一个作为参数传入的方法,否则就不进行任何操作。
public void ifPresent(Consumer<? super T> consumer) {
if (value != null)
consumer.accept(value);
}
两个Optional对象的组合
现在,我们假设你有这样一个方法,它接受一个Person和一个Car对象,并以此为条件对外部提供的服务进行查询,通过一些复杂的业务逻辑,试图找到满足该组合的最便宜的保险公司:
public Insurance findCheapestInsurance(Person person, Car car) {
// 不同的保险公司提供的查询服务
// 对比所有数据
return cheapestCompany;
}
我们还假设你想要该方法的一个null-安全的版本,它接受两个Optional
对象作为参数, 返回值是一个Optional<Insurance>
对象,如果传入的任何一个参数值为空,它的返回值亦为空。Optional类还提供了一个isPresent
方法,如果Optional
对象包含值,该方法就返回true, 所以你的第一想法可能是通过下面这种方式实现该方法:
public Optional<Insurance> nullSafeFindCheapestInsurance(Optional<Person> person, Optional<Car> car) {
if (person.isPresent() && car.isPresent()) {
return Optional.of(findCheapestInsurance(person.get(), car.get()));
} else {
return Optional.empty();
}
}
这个方法具有明显的优势,我们从它的签名就能非常清楚地知道无论是person还是car,它的值都有可能为空,出现这种情况时,方法的返回值也不会包含任何值。不幸的是,该方法的具体实现和你之前曾经实现的null检查太相似了:方法接受一个Person和一个Car对象作为参数, 而二者都有可能为null。利用Optional类提供的特性,有没有更好或更地道的方式来实现这个方法呢?
public Optional<Insurance> nullSafeFindCheapestInsurance(Optional<Person> person, Optional<Car> car) {
return person.flatMap(p -> car.map(c -> findCheapestInsurance(p, c)));
}
这段代码中,你对第一个Optional
对象调用flatMap
方法,如果它是个空值,传递给它的Lambda表达式不会执行,这次调用会直接返回一个空的Optional
对象。反之,如果person
对象存在,这次调用就会将其作为函数Function
的输入,并按照与flatMap
方法的约定返回一个Optional<Insurance>
对象。这个函数的函数体会对第二个Optional
对象执行map
操作, 如果第二个对象不包含car
,函数Function
就返回一个空的Optional
对象, 整个nullSafeFindCheapestInsuranc
方法的返回值也是一个空的Optional
对象。最后,如果person
和car
对象都存在,作为参数传递给map
方法的Lambda表达式能够使用这两个值安全地调用原始的findCheapestInsurance
方法,完成期望的操作。
流与Optional
我们开发过程中有时候经常会用到Optional
和Stream
的结合。我们知道当list
为空时是不会进入流执行,但是为null
的时候调用stream
是会有空指针异常的。所以我们就在orElse()
上new一个list返回这样就可以将整个流程串起来了!
List<User> users = Lists.newArrayList();
Optional.ofNullable(users)
.filter(CollectionUtils::isNotEmpty)
.orElse(Lists.newArrayList())
.stream()
.map(User::getAddress)
.collect(Collectors.toList());
Optional使用建议
1.调用get()
注意和isPresent()
方法配合食用,否则可能会抛出异常
2.尽量不要将Optional
类型用作属性或是方法参数,Optional
类型不可被序列化, 因为它也并未实现Serializable
接口,所以用作字段类型可能会出问题
3.尽量进行链式调用,简化代码