如何优雅的判空|2021上半年最后一文|30天日更小结

这是我参与更文挑战的第 30 天,活动详情查看: 更文挑战


掘金30天更文终于要结束了, 连续写30篇文章我觉得是很难的,幸亏以前存了一大部分,不然就按照我的肝肯定是肝不动的,

换个思路想想,只看连续更文的话也不难,大多数最开始想到的应该就是力扣刷题,这种题库、解法、语言都很多,足够完成任务,

所以我们也能看到掘金坚持到30天的有很多是算法题解,这也是本次活动被诟病最多的地方,“水文”太多,

但是只要文章是经过自己大脑思考后产物、代码每一行自己都亲手敲过、文章能够保证正确性,

就算不高深对作者、对读者也是有意义的,

日更活动其他站点比如简书也有,此类活动更多的是培养一种持续学习输入与输出的习惯,

要想有持续高质量的输出,就需要压制追求安逸的心、就需要啃晦涩的书籍、就需要debug头疼的源码

如果不学习新知识,一个人的储备我觉得可能30天就足矣被榨干

你我皆凡人,今天多学一点本事,明天就少说一句求人的话

你要悄悄拔尖,然后惊艳所有人

优雅的判空

Java8 增加了很多有用的API,其他的不说,今天只说说 Optional

仅是解决NPE问题吗?

简单看过Optional说明的同学可能认为它是解决NPE(NullPointExcepiton)问题的,于是代码是这样:

List<Order> getOrders(User u) {
    Optional<User> user = Optional.ofNullable(u);
    if (user.isPresent()) {
        return user.get().getOrders();
    }
    return Collections.emptyList();
}
复制代码

这样的写法对吗?
对,没问题,可这样与我们原来的写法有区别吗?

List<Order> getOrders(User u) {
    if (u != null) {
        return user.getOrders();
    }
    return Collections.emptyList();
}
复制代码

其实没区别,因为思维还在原地踏步,本能的认为不过是User 实例的包装。

那既然一样,使用Optional需要写的还更多一些,这个API不就是没有意义了吗?

那是我们没有理解设计者的意图,没有优雅使用!

如何优雅?

当切换到Java 8 的 Optional 时, 不能继承性的对待过往 null 时的那种思维, 应该掌握好新的, 正确的使用姿势.

需要了解一点点java函数编程的知识,附录有Optional 用到的函数接口的说明

在使用正确的姿势前,错误的姿势有哪些?

  1. 调用isPresent()方法
  2. 调用get()方法
  3. Optional 类型作为类/实例属性
  4. Optional 类型作为方法参数

解释下:

  1. 使用isPresent() 和使用obj!=null 无任何分别
  2. 直接调用get()方法虽然不抛出NPE了,但如果没有值是会抛出NoSuchElementException。
    这个方法的源码如下:
  /**
     * If a value is present in this {@code Optional}, returns the value,
     * otherwise throws {@code NoSuchElementException}.
     *
     * @return the non-null value held by this {@code Optional}
     * @throws NoSuchElementException if there is no value present
     *
     * @see Optional#isPresent()
     */
public T get() {
    if (value == null) {
        throw new NoSuchElementException("No value present");
    }
    return value;
}
复制代码

3和4 是将Optional类型作为属性或是方法参数,这样使用更是不可取。

所以Optional 中可以依赖的应该是除了isPresent()和get()的其他方法:

方法 描述
public<U> Optional<U> map(Function<? super T, ? extends U> mapper) 如果值存在,就对该值执行提供的mapping函数的调用
public T orElse(T other) 如果有值则将其返回,否则返回一个默认值
public T orElseGet(Supplier<? extends T> other) 如果有值则将其返回,否则返回一个由指定的Supplier接口生成的值
public void ifPresent(Consumer<? super T> consumer) 如果值存在,就执行使用该值的方法调用,否则什么也不做
public Optional<T> filter(Predicate<? super T> predicate) 如果值存在并且满足提供的谓词,就返回包含该值的Optional对象
public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) 如果值存在,就对该值执行提供的mapping函数调用
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X 如果有值则将其返回,否则抛出一个由指定的Supplier接口生成的异常

在介绍以上7个方法之前先提Optional的三种构造方式:

  • Optional.of(obj)
  • Optional.ofNullable(obj)
  • Optional.empty()

Optional.of(obj): 它要求传入的 obj 不能是 null 值的, 否则还没开始进入角色就倒在了 NullPointerException 异常上了.

Optional.ofNullable(obj): 它以一种智能的, 宽容的方式来构造一个 Optional 实例. 来者不拒, 传 null 进到就得到 Optional.empty(), 非 null 就调用 Optional.of(obj).

Optional.empty():空值的Optional

那是不是我们只要用 Optional.ofNullable(obj) 一劳永逸, 以不变应二变的方式来构造 Optional 实例就行了呢?
那也未必, 否则 Optional.of(obj) 何必如此暴露呢, 私有则可?

可以这样理解:
当我们非常非常的明确将要传给 Optional.of(obj) 的 obj 参数不可能为 null 时, 比如它是一个刚 new 出来的对象(Optional.of(new User(…))), 或者是一个非 null 常量时; 2. 当想为 obj 断言不为 null 时, 即我们想在万一 obj 为 null 立即报告 NullPointException 异常, 立即修改, 而不是隐藏空指针异常时, 我们就应该果断的用 Optional.of(obj) 来构造 Optional 实例, 而不让任何不可预计的 null 值有可乘之机隐身于 Optional 中.

现在才开始怎么去使用一个已有的 Optional 实例, 假定我们有一个实例 Optional user, 下面是几个普遍的, 应避免 if(user.isPresent()) { … } else { … } 几中应用方式.

存在即返回, 无则提供默认值
return user.orElse(null);  //而不是 return user.isPresent() ? user.get() : null;
return user.orElse(UNKNOWN_USER);
复制代码
存在即返回, 无则由函数来产生
return user.orElseGet(() -> fetchAUserFromDatabase()); 
//而不要 return user.isPresent() ? user: fetchAUserFromDatabase();
复制代码
存在才对它做点什么
user.ifPresent(System.out::println);
//而不要下边那样
if (user.isPresent()) {  
	System.out.println(user.get());
}
复制代码
map函数的使用

当 user.isPresent() 为真, 获得它关联的 orders, 为假则返回一个空集合时, 我们用上面的 orElse, orElseGet 方法都乏力时, map函数就可以出马了, 我们可以这样一行:

return user.map(u -> u.getOrders()).orElse(Collections.emptyList())
复制代码

而java 8之前的写法:

if (user.isPresent()) {
    return user.get().getOrders();
} else {
    return Collections.emptyList();
}
复制代码

而且map是可以级联的,在深一层:

return user.map(u -> u.getUsername()).map(name -> name.toUpperCase()).orElse(null);
复制代码

这要是放到以前我们得这么写:

User user = new User();
if (user != null) {
    String name = user.getUsername();
    if (name != null) {
        return name.toUpperCase();
    } else {
        return null;
    }
} else {
    return null;
}
复制代码

用了 isPresent() 处理 NullPointerException 不叫优雅, 有了 orElse, orElseGet 等, 特别是 map 方法才叫优雅.

filter的使用
//filter方法检查给定的Option值是否满足某些条件。//如果满足则返回同一个Option实例,否则返回空Optional。Optional<String> longName = name.filter((value) -> value.length() > 6);System.out.println(longName.orElse("The name is less than 6 characters"));//输出Sanaulla//另一个例子是Optional值不满足filter指定的条件。Optional<String> anotherName = Optional.of("Sana");Optional<String> shortName = anotherName.filter((value) -> value.length() > 6);//输出:name长度不足6字符System.out.println(shortName.orElse("The name is less than 6 characters"));
复制代码
orElseThrow的使用
return user.orElseThrow(()->new NullPointException("No user"));
复制代码

or

return user.orElseThrow(NullPointException::new);
复制代码

一句话小结: 使用 Optional 时尽量不直接调用 Optional.get() 方法, Optional.isPresent() 更应该被视为一个私有方法, 应依赖于其他像 Optional.orElse(), Optional.orElseGet(), Optional.map() 等这样的方法.

附:Optional用到的函数接口
函数接口 说明
Predicates Predicate是一个布尔类型的函数,该函数只有一个输入参数
Function Function接口接收一个参数,并返回单一的结果
Supplier Supplier接口产生一个给定类型的结果。与Function不同的是,Supplier没有输入参数。
Consumer Consumer代表了在一个输入参数上需要进行的操作
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享