1、平时开发常见的取值方法
Java开发中,获取值的时候最常想到的应该就是直接使用.获取,如:
// 从用户信息实例中的证件信息实例获取用户姓名
userInfo.getIdCardInfo().getName()
这样有时候会造成NPE(NullPointException)这种常见且尴尬的异常,所以为了健壮性,往往会做如下改造:
String name = "";
if (userInfo != null && userInfo.getIdCardInfo() != null) {
name = userInfo.getIdCardInfo().getName();
}
System.out.println(name);
甚至,如果新需求是当name为空时,应该抛出个错误,那必不可少要多一个判断:
String name = "";
if (userInfo != null && userInfo.getIdCardInfo() != null) {
name = userInfo.getIdCardInfo().getName();
}
System.out.println(name);
if (StringUtils.isEmpty(name)) {
throw new RuntimeException("哎呀,姓名参数是空的。");
}
2、Optional如何优雅的处理NPE
先上代码
Optional<UserInfo> userInfoOptional = Optional.ofNullable(userInfo);
String name = userInfoOptional.map(UserInfo::getIdCardInfo)
.map(IDCardInfo::getName)
.orElseThrow(() -> new RuntimeException("哎呀,姓名参数是空的。"));
惊不惊喜意不意外??两行代码就能搞定非空判断 && 取值 && 异常抛出,甚至可以将代码简化为一行
Optional.ofNullable(userInfo)
.map(UserInfo::getIdCardInfo)
.map(IDCardInfo::getName)
.orElseThrow(() -> new RuntimeException("哎呀,姓名参数是空的。"));
没有if-else做非空判断,使代码看起来更加地优雅简洁。如果代码暂时看不懂,没关系,我们继续探究Optional中的各个方法。
3、Optional方法介绍
3.1 构造方法
Optional类的构造方法为private,所以不能直接通过new得到一个Optional实例。Optional为我们提供了三种创建实例的静态方法。
(1)empty()
private static final Optional<?> EMPTY = new Optional<>();
/**
* 返回一个空的Optional实例,空实例即为Optional不包含值
*/
public static<T> Optional<T> empty() {
@SuppressWarnings("unchecked")
Optional<T> t = (Optional<T>) EMPTY;
return t;
}
(2)of(T value)
public static <T> Optional<T> of(T value) {
return new Optional<>(value);
}
可以构造一个包含非空(non-null)值的Optional实例,如果传入的值为null,会抛出NPE异常
(3)ofNullable(T value)
public static <T> Optional<T> ofNullable(T value) {
return value == null ? empty() : of(value);
}
由源码可知,该静态方法根据传入值是否为null进行三目运算,分情况调用empty()和of(value)。所以传入值为null时,也不是真的为null,而是创建一个不包含值的Optional容器。
3.2 相关方法
(1)map() && flatMap()
public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
// 非空判断,如果为空,直接抛出NPE异常
Objects.requireNonNull(mapper);
// 如果值为空,返回不包含值的Optional实例
if (!isPresent())
return empty();
else {
// 否则返回使用Optional包装的值
return Optional.ofNullable(mapper.apply(value));
}
}
如果当前的Optional不包含值,即empty(),则继续返回不包含值的Optional。否则,返回一个新的Optional,该Optional包含mapper函数输入的值。例如上边的例子:
Optional.ofNullable(userInfo)
// 返回IdCardInfo对象,且使用Optional包装,即为Optional<IDCardInfo>
.map(UserInfo::getIdCardInfo)
// 返回使用Optional包装的String字符串,即为Optional<String>
.map(IDCardInfo::getName)
.orElseThrow(() -> new RuntimeException("哎呀,姓名参数是空的。"));
**注意:**如果输入的值已经被Optional包装,即userInfo.getIdCardInfo()返回的是Optional<IdCardInfo>,那么使用map()后,返回的值将为Optional<Optional<IdCardInfo>>。
那么如何避免上面这种取值出现嵌套两层Optional的情况呢,就要使用我们接下来讲的flatMap()方法。
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));
}
}
从源码可以看出,flatMap() 与 map()方法基本一致,只是返回的时候,不再使用Optional包装,而是将mapper函数输入的值直接返回。所以,当userInfo.getIdCardInfo()返回的是Optional<IdCardInfo>时,使用flatMap()方法,返回的值为Optional<IdCardInfo>
(2)ifPresent() && isPresent()
public void ifPresent(Consumer<? super T> consumer) {
if (value != null)
consumer.accept(value);
}
该方法用来判断Optional中是否包含值,如果有,则执行consumer.accept()操作,如果没有,不操作。
Consumer为一个lamda表达式,如:name -> System.out.println(name);
**示例:**如果IdCardInfo实例存在name的值,就将它打印出来
Optional<UserInfo> userInfoOptional = Optional.ofNullable(userInfo);
userInfoOptional.map(UserInfo::getIdCardInfo)
.map(IDCardInfo::getName)
// 如果只是单纯打印name值,以下代码可以改写为
// ifPresent(System.out::println)
.ifPresent(name -> System.out.println(name));
isPresent()与ifPresent()只有一个字母的差别,作用也有所差异,从名字也可以看出,isPresent()是判断Optional是否存在值,没有入参,返回值是boolean类型。
public boolean isPresent() {
return value != null;
}
(3)filter()
public Optional<T> filter(Predicate<? super T> predicate) {
// 非空判断
Objects.requireNonNull(predicate);
// 如果不包含值,则返回当前对象
if (!isPresent())
return this;
else
// 如果predicate判断条件与value相同,则返回当前对象,否则返回empty()
return predicate.test(value) ? this : empty();
}
filter()与Stream流的filter()作用类似,都是用来筛选符合条件的数据。这里的filter()如果值符合传入的条件,则返回当前对象,否则返回一个不包含值的Optional实例。
**示例:**如果IdCardInfo实例存在name为yostar的值,就将它打印出来
Optional<UserInfo> userInfoOptional = Optional.ofNullable(userInfo);
userInfoOptional.map(UserInfo::getIdCardInfo)
.map(IDCardInfo::getName)
// 以下代码也可以为:filter(name -> "yostar".equals(name))
.filter("yostar"::equals)
.ifPresent(System.out::println);
(4)get()
public T get() {
if (value == null) {
throw new NoSuchElementException("No value present");
}
return value;
}
使用get()来获取当前Optional实例的值,如果当前实例不包含值,会抛出NoSuchElementException异常,否则,直接返回该实例包含的值。示例如下
// 获取IdCardInfo中的name值
Optional<UserInfo> userInfoOptional = Optional.ofNullable(userInfo);
String name = userInfoOptional.map(UserInfo::getIdCardInfo)
.map(IDCardInfo::getName)
.get();
System.out.println(name);
(5)orElse() && orElseGet()
orElse() 与 orElseGet()类似,都为设置默认值,不同的是orElse()接收的参数是任意类型T,而orElseGet() 接收的参数为一个Supplier函数式接口
Consumer为消费型函数式接口,没有返回值
Supplier为供给型函数式接口,返回值为T
**示例:**如果IdCardInfo实例存在name的值,就将它打印出来,如果不存在,赋予默认值yostar
Optional<UserInfo> userInfoOptional = Optional.ofNullable(userInfo);
String name = userInfoOptional.map(UserInfo::getIdCardInfo)
.map(IDCardInfo::getName)
// .orElse("yostar")
.orElseGet(() -> "yostar");
ystem.out.println(name);
(6)orElseThrow()
该方法与orElse()的区别在于,该方法当Optional中包含值时,返回值,不包含值时,抛出自己传入的异常。
示例: 如文章开头的示例,当IdCardInfo实例中不存在name信息时,抛出异常。
Optional.ofNullable(userInfo)
.map(UserInfo::getIdCardInfo)
.map(IDCardInfo::getName)
.orElseThrow(() -> new RuntimeException("哎呀,姓名参数是空的。"));
4、总结
Optional是Java8开始用来代替NULL的一个容器对象,可以很好很优雅的减少程序的空指针异常。
Optional设计十分精细,很自然的融入Java8支持的函数式编程。