Java8新特性4:Optional—一文详解java1.8的Optional Api的使用,使用Optional杜绝空指针异常的出现。

一、null带来的种种弊端,为什么java8要让我们避免在代码中引用null?

《java8 in action》作者在正式引出Optional前,先指出了我们java开发人员在开发中必可避免的会出现很多对于null 的引用,并且时常会在程序运行时出现很多空指针异常,一个在java8以前,无论是老手还是新手都需要面临的一个棘手的异常。

Java8新特性4:Optional—一文详解java1.8的Optional Api的使用,使用Optional杜绝空指针异常的出现。_第1张图片

1、模拟一个场景,看看直接引用null会有哪些弊端

《java8 in action》书里举了一个例子:假设有个程序,这个程序可以查询一个person的car的Insurance(保险)的保险公司名字。这个程序源码如下:

Java8新特性4:Optional—一文详解java1.8的Optional Api的使用,使用Optional杜绝空指针异常的出现。_第2张图片

然后作者提了个问题:此时main方法里运行下面这段代码会有什么问题呢?

显然这时会直接给你报一个空指针异常:NullPointerException,并且程序会出现意外中断。原因在于这里我们的传进去的person是空的,而且在现实中,我们在编码过程中或基于对现实业务的理解和认知,提前判断,比如上面这个例子中,你会想到:即便传入person不为空,但你也无法保证它下面的car,car下面的Insurance不为空,因为一个客户可能没有车,有车他也可能没有买保险,但是如果有保险,保险公司的名字一定是有的。

所以为了处理这种可能为空的情况,一般大家会有下面两种处理方式:

  • 多层嵌套判断null:

Java8新特性4:Optional—一文详解java1.8的Optional Api的使用,使用Optional杜绝空指针异常的出现。_第3张图片

  • 退出式的null判断

Java8新特性4:Optional—一文详解java1.8的Optional Api的使用,使用Optional杜绝空指针异常的出现。_第4张图片

上面这个案例展现出了我们曾频繁使用的null判断方式,同时也展现出它具有的一些隐藏的“bug”,也就是java8为什么不在推荐我们引用null:

  • 代码可扩展性差
  • 代码可读性差,复杂需求情况下,你的代码可能不得不充斥着各种深度嵌套的null检查
  • 代码冗杂,可维护性差
  • 这种判断流程易出错,你可能会漏掉一些可能为空的业务情况(这一点相信大家深有感触)
  • null 破坏了Java的编程哲学,它一直致力于消除指针,但null却成为了唯一的例外是:空指针。

二、java.util.Optional入门

java.util.Optional这个类的作用其实就是用来封装那些可能为null的值(对象),怎么理解这句话呢?

基于上面那个汽车例子:我们知道一个人可能有也可能没有车,所以上面例子中Person类内部的car变量就不应该直接声明为Car(不然程序某个人没有车时就会给car赋一个null值,然后你又开始陷入空判断)。java8以后,推荐我们遇到这种情况时,应该直接将Car声明为Optional类型,如何声明后面讲。

  • 当car变量存在值时,Optional类只是对car类进行一个简单封装:

Java8新特性4:Optional—一文详解java1.8的Optional Api的使用,使用Optional杜绝空指针异常的出现。_第5张图片

  • car变量不存在值时,即此时car对象为null,car会被重新建模成一个“空”的Optional对象:

Java8新特性4:Optional—一文详解java1.8的Optional Api的使用,使用Optional杜绝空指针异常的出现。_第6张图片

此时这种情况下就凸显了Optional的独特作用了:当car被optional包裹,且值为空时,Optional会通过一个静态工厂方法   Optional.empty()返回一个空的Optional对象。那么引用空的optional对象和直接引用null有啥区别? 第一个首要区别就是,如果你直接引用一个null , 一定会触发NullPointerException 空指针异常;但是使用Optional.empty()就完全没事儿,它返回的是一个有效的空的Optional对象,这个对象可被调用,不会触发空指针异常。第二个区别就是,使用Optional方式申明变量能让人更加直接明了知道,这个变量是可能存在空值的,但是我们允许他存在空值,代码可读性高,并且我们不必要对他可能存在的空值进行if null 的判断,我们也就避免了if null 漏判断的情况。

现在我们使用Optional对开始那个汽车案例进行代码优化:

    @Data
    public static class Person {
        private Optional car;//人可能有车,也可能没有车,因此将这个字段声明为Optional
    }
    @Data
    public static class Car {
        private Optional insurance;//车可能买了保险,也可能没有保险,所以将这个字段声明为Optional
    }
    @Data
    public static class Insurance {
        private String name;//保险公司必须有名字,所以不进行optional包装。
    }

上面这段代码一个意义就是,当你引用insurance公司名称时,如果发生NullPointerException,你能极快知道这是由于你的数据出现了问题,而不是代码问题,所以你也不必急于添加一个if null的检查语句隐藏这个空指针问题。

三、详解Optional API

1、如何创建Optional对象

  • 通过静态工厂方法Optional.empty 声明一个空的Optional对象
Optional optCar = Optional.empty();
  • 使用静态工厂方法Optional.of,依据一个非空值创建一个Optional对象
Car car = new Car();
Optional car1 = Optional.of(car);

如果你不小心给它传了一个null值,它会直接提示你代码有问题:

  • 使用静态工厂方法Optional.ofNullable,你可以创建一个允许null值的Optional对象
Optional car2 = Optional.ofNullable(null);

2、如何获取Optional变量中的值

  • .get()

.get()是最简单但又最不安全的方法。如果变量值存在,它直接返回封装的变量值,否则就抛出一个NoSuchElementException异常。所以,除非你非常确定Optional变量一定包含值,否则别用这个方法,这种方式即便相对于嵌套式的null检查,也并未体现出多大的改进。

Java8新特性4:Optional—一文详解java1.8的Optional Api的使用,使用Optional杜绝空指针异常的出现。_第7张图片

  • .orElse(T other)

Optional对象不包含值时返回一个默认值。

Java8新特性4:Optional—一文详解java1.8的Optional Api的使用,使用Optional杜绝空指针异常的出现。_第8张图片

  • .orElseGet(Supplier other)

这个相当于 .orElse() 的升级版,如果创建默认值比较耗时费力,使用此方法可以提升程序的性能。Supplier类型接口的抽象方法提供的是一个无参返回一个返回值的抽象方法,这里你可以使用方法引用,如下:

  • .orElseThrow(Supplier exceptionSupplier)

Optional对象为空时,你可以自己定制一个异常并抛出,如下:

Java8新特性4:Optional—一文详解java1.8的Optional Api的使用,使用Optional杜绝空指针异常的出现。_第9张图片

  • .ifPresent(Consumer)

变量值存在时执行一个作为参数传入的方法,否则就不进行任何操作。

Java8新特性4:Optional—一文详解java1.8的Optional Api的使用,使用Optional杜绝空指针异常的出现。_第10张图片

3、Optional使用filter 过滤掉特定的值

你可以吧Optional看成只包含一个元素的Stream,也就是说Optional的filter用法和Stream的filter用法神似。

举个例子:当保险存在,且保险公司名字为空时,我们输出一句话:

Java8新特性4:Optional—一文详解java1.8的Optional Api的使用,使用Optional杜绝空指针异常的出现。_第11张图片

 

Optional 的 filter方法接受一个函数作为参数:,如果Optional对象的值存在,并且它符合你给定的函数条件,filter方法就返回其值;否则它就返回一个空的Optional对象。

4、使用Optional 的map 从Optional 对象中提取值、将提取出的值转换为Optional值

Java8新特性4:Optional—一文详解java1.8的Optional Api的使用,使用Optional杜绝空指针异常的出现。_第12张图片

上面这个就是Optional的map方法的源码,从源码中我们可以知道,map方法接受一个function函数作为参数,根据function函数内容返回一个Optional对象,比如像这样:

5、使用Optional 的flatMap API使用原理,使用Optional 的flatMap扁平化 Optional 对象,

看上面map源码,我们知道:Optional 的 map返回的是一个被Optional包装的 function函数的返回值 U的对象。所以如果你有如下需求(一层层解析出一个上级对象里包裹的各级下级对象,这些小对象以属性方式存在上级对象中),并且想进行多次map操作来实现这个需求,但是这种做法是非法的:

Java8新特性4:Optional—一文详解java1.8的Optional Api的使用,使用Optional杜绝空指针异常的出现。_第13张图片

上面这个想法很美好,但是实际是行不通的,原因是还是在map的返回值上,map操作后,返回的永远是它的被Optional包裹的function函数的返回值,所以多次map后,你得到就是多次多层嵌套在Optional里面的值,这种值你当然不能在进行这种直接调用了:(语文不好,可能描述不清楚)把上面代码拆开看看,自己意会吧:

Java8新特性4:Optional—一文详解java1.8的Optional Api的使用,使用Optional杜绝空指针异常的出现。_第14张图片

总之,map不满足我们复杂的需求,于是有了Optional的flapMap方法。我们直接来看flapMap的源码:

Java8新特性4:Optional—一文详解java1.8的Optional Api的使用,使用Optional杜绝空指针异常的出现。_第15张图片

flapMap返回的直接就是他的参数function函数里的,也就是说,无论你在一个Optional对象上进行多少次flapMap操作,它返回给你的始终都是被单一Optional包括的值。下面这个图可以简单明了说明map与flapMap区别:

Java8新特性4:Optional—一文详解java1.8的Optional Api的使用,使用Optional杜绝空指针异常的出现。_第16张图片

好了,现在我们对上面那个多次map操作的程序进行类似下面代码的重写(先别管报错,只是让大家先清晰flapMap的用法):

Java8新特性4:Optional—一文详解java1.8的Optional Api的使用,使用Optional杜绝空指针异常的出现。_第17张图片

 

拓展:

为什么使用Optional封装了 Person 、Car 、Insurance对象后,还是会报空指针异常呢??

首先,我们要断点到flapMap方法的源码看看,弄清楚这个NullPointerException是哪里报出来的:

《1》排除flapMap(function T)的function参数问题

《2》继续断点往下看:

Java8新特性4:Optional—一文详解java1.8的Optional Api的使用,使用Optional杜绝空指针异常的出现。_第18张图片

到这里,我们发现传进来的Person进过了null判断,它不是空的,此时我们进入requireNonNull这个方法继续断点。

《3》我们发现在这里requireNonNull(obj)这个方法报错了:

它里面的参数obj为null,所以报了空指针。

Java8新特性4:Optional—一文详解java1.8的Optional Api的使用,使用Optional杜绝空指针异常的出现。_第19张图片

这个方法里的obj其实就是指Person里面定义的:Optional car

我们再回到我们自己的代码,不难理解为什么此时用了Optional包裹car对象,还是报了空指针:

.flatMap(Person::getCar)里面的Person::getCar返回的是一个null,而且注意返回只是一个没有Optional衣服的null,那么就相当于.flatMap(null),而null在没有被Optional包装时,作为参数传给flatMap时是会报空指针异常的,它不会被Optional藏起来 。

  • .flatMap源码:

  • 我的代码:

Java8新特性4:Optional—一文详解java1.8的Optional Api的使用,使用Optional杜绝空指针异常的出现。_第20张图片

解决办法:

那么如何真正利用Optional杜绝空指针的出现呢?

上面我们说了Person::getCar返回的是没被Optional包起来的nul,所以报错,那我们就把这些null,或者说参数值可能为null的都用Optional包起来,所以我重构一个有效可行的代码,真正实现利用Optional杜绝空指针异常:

        Person person = new Person();
/* 
        Car car = new Car(); 
        Insurance in = new Insurance();
        car.setInsurance(Optional.of(in));
        person.setCar(Optional.of(car));*/

   
        String s = Optional.ofNullable(person)
                .flatMap(p -> Optional.ofNullable(p.getCar()))
                .flatMap(c -> Optional.ofNullable(c.get().getInsurance()))
                .map(i -> i.get().getName())
                .orElse("多次flapmap后,发现这个保险没有名字");
        System.out.println(s);

 

 

你可能感兴趣的:(Java8-NewThings,java8,Optional)