阿里心法之alibaba Java开发手册 -- Optional类

阿里心法之alibaba Java开发手册 – Optional类

目录

文章目录

  • 阿里心法之alibaba Java开发手册 -- Optional类
    • 目录
    • 引言
    • 场景
    • Optional类的使用
      • Optional类实例的创建
      • 返回默认值或异常
        • orElse()和orELseGet()
          • orElse()与orElseGet()的不同之处
        • orElseThrow()
      • 对Optional包装的对象进行操作
    • 使用Optional对场景进行优化

引言

在《阿里巴巴 Java开发手册》第二章异常处理的第10条中提到:

防止NPE,是程序员的基本修养,注意NPE产生的场景

其中第6项说到:

(6) 级联调用 obj.getA().getB().getC();一连串调用,易产生 NPE。
正例:使用 JDK8 的 Optional 类来防止 NPE 问题。

NPE就是NullPointerException,也是我们日常开发中经常会碰到的几种异常之一,在级联调用的处理上尤其头疼,通过对Optional类的了解和使用发现,确实在级联调用中,预防和处理NPE问题有了更优雅的解决方式,下面让我们一起来了解以下。

场景

在我们日常的编码过程中,经常会遇到级联调用的情况,当使用级联调用时,我们首先应该反射性的想到,可能会出现NPE的问题,比如下面的示例代码:


BigDecimal decimalNumber = d.getC().getB().getA().getNumber();

在d、c、b、a、number都有可能为null的情况下,我们的代码该怎么写呢?


// 为number赋默认值0
BigDecimal decimalNumber = BigDecimal.ZERO;
// 开始进行null判断
if (d != null) {
    C c = d.getC();
    if (c != null) {
        B b = c.getB();
        if (b != null) {
            A a = b.getA();
            if (a != null) {
                number = a.getNumber();
                if (number != null) {
                    decimalNumber = number;
                }
            }
        }
    }
}

这样的代码写出来非常的不美观,并且逻辑稍微复杂一点,也会极大的降低代码的可维护性,那么针对这样的情况,如何才能写出更加优雅的代码呢,下面就是JDK 1.8中引入的Optional类登场的时候了。

Optional类的使用

Optional是一个有可选值的包装类,我们主要关心它的三个操作:创建实例、指定默认值、操作包装的对象。

Optional类实例的创建

Optional类的构造方法是私有方法,只能通过Optional.of()和Optional.ofNullable()方法来创建包含值的Optional类实例,当然也可以通过Optional.empty()方法获得不含值的类实例,但对于今天的主题没有太多意义,所以也不进行详细的介绍。

of()和ofNullable()方法的区别在于,of()方法不允许传入null值,若传入null值,则在创建对象时就会抛出NullPointerException异常,而ofNullable()从名字上也可以明显的看出,可以传入null值。两个方法的用法如下:

Optional userOptional = Optional.of(user);
Optional userNullableOptional = Optional.ofNullable(user);

读到这里,可能不太理解为什么会有这两个方法,也不明白他们在实际使用意义上的差别,其实在本文所述的场景中ofNullable()方法使用的较多,不要急,接着往下看

返回默认值或异常

Optional类主要解决null值问题,出现null值的时候的行为(默认值或异常)的指定也是通过调用一些方法进行指定的。orElse()和orElseGet()方法用来指定默认值,orElseThrow()方法用来指定对于null值出现抛出的异常。

在对他们分别进行讲解的时候需要提前注意的是:这些方法还有一个重要的作用就是解包,也就是将Optional类中包装的值,还原出来。下面是对上述三个方法的讲解:

orElse()和orELseGet()

orElse()用法如下:

User user1 = new User("[email protected]", "1234");
User user2 = null;
User default = new User("[email protected]", "default");

User result1 = Optional.ofNullable(user1).orElse(default);
User result2 = Optional.ofNullable(user2).orElse(default);
// 最终结果
// result1 == user1 is true
// result2 == default is true
// result2 != null is true

orElseGet()用法如下:

User user1 = new User("[email protected]", "1234");
User user2 = null;
User default = new User("[email protected]", "default");

User result1 = Optional.ofNullable(user1).orElseGet(() -> default);
User result2 = Optional.ofNullable(user2).orElseGet(() -> default);
// 最终结果
// result1 == user1 is true
// result2 == default is true
// result2 != null is true

两者的使用方法如此近似,那么具体有什么区别呢?

orElse()与orElseGet()的不同之处

orElse()方法与orElseGet()方法的差别根本源自于其接受的参数不同,orElse()方法接受一个类对象并将这个对象作为默认值,而orElseGet()方法接受一个Supplier对象,也就是函数式接口。

两个方法的内部实现都相同,都是首先判断包装对象是否为null,若为null就才会执行传入参数的操作,但是如果出现以下情况:

User result1 = Optional.ofNullable(user).orElse(createNewUser());
User result2 = Optional.ofNullable(user).orElseGet(() -> createNewUser());

orElse()方法会先计算出createNewUser()函数的值,然后执行判断。
而orElseGet()方法则会先执行判断,满足条件时才会执行对应的函数并返回值。

所以对于调用频率非常高的情况时,使用orElseGet()方法就会带来极大的性能提升。

orElseThrow()

orElseThrow()方法与orElseGet()方法非常类似,同样接收一个Supplier对象参数,使用方法也非常类似,只不过用来在包装值为null的时候抛出异常,而不是返回默认值。

用法如下:

User result1 = Optional.ofNullable(user).orElseThrow(() -> new IllegalArgumentException());
// An IllegalArgumentException will thrown when user is null

看到这里可能对Optional的作用还是不清楚,别急,只要记住orElse一类的方法使用来指定默认值或抛出异常的,同时具有解包的作用

对Optional包装的对象进行操作

对于对Optional类中包装的对象进行操作,Optional提供了map()和flatMap()两个方法,我们主要介绍map()方法,至于flatMap()的使用,有兴趣的可以去网上搜索以下。

map()方法接受一个以包装的对象作为参数的Lamda表达式,并将执行的结果包装为Optional对象来返回,注意后面一点非常的重要,它为我们实现级联调用map()方法提供了可能。

我们先来看它如何使用:

String email = Optional.ofNullable(user).map(u -> u.getEmail()).orElse("[email protected]");

在上述代码中:

  • 当user为null时,map()方法返回一个包含null的Optional类,并调用orElse()方法返回默认值
  • 当user.getEmail()为null时,由于map()方法会将Lamda表达式的值包装进Optional类中返回,则调用orElse()方法,仍然返回默认值。
  • 若user和user.getEmail()都不为空时,map()方法调用后将返回包含对应值的Optional对象,调用orElse()方法解包后,将得到对应的user.getEmail()的值。

使用Optional对场景进行优化

通过上面的讲解我们也就可以看出,Optional类中提供的方法可以对应到级联调用之中,首先使用of*()的方法创建一个Optional包装类对象,便于开始调用实例方法;然后使用 *map() 方法对所包装的对象进行操作,并且返回值仍为Optional类对象,所以可以级联的调用,并且不用担心NPE的发生;最后,使用orElse()方法指定默认结果的同时对Optional对象进行解包,根据Optional对象中包装的值是否为null,决定得到对应值还是默认值。

下面就是对文章开始给出的场景,使用Optional类后的优化结果:

BigDecimal decimalNumber = Optional.ofNullable(c).map(C::getB).map(B::getA()).map(A::getNumber).orElse(BigDecimal.ZERO);

优化后的代码,简洁便于阅读也便于维护。


如有错误,欢迎指摘。欢迎通过各种渠道与我讨论,也欢迎通过左上角的“向TA提问”按钮问我问题,我将竭力解答你的疑惑。

你可能感兴趣的:(Think,in,Java)