本文转载自微信公众号《编程新视野》,作者是二师兄。转载本文请联系程序新视界公众号。PrefaceJava8提供了很多新的特性,但是很多朋友没有关注到这一点,还是老样子的写法。最近看了很多开源框架的源码,发现很多Java8的API都被频繁使用了。以Nacos框架为例,已经有了典型的Optional用例,场景把握的很好。如果此时还没有意识到需要学习理解,以后阅读源码可能会有点吃力。在今天的文章中,我们将以Nacos中Optional的使用为例,深入讲解Optional的使用。有很多老规矩、源码、案例、实战。Nacos中的Optional在Nacos中使用。Nacos中有这样一个接口ConsistencyService,用来定义一致性服务。其中一种方法返回的类型是可选的:/***Gettheerrormessageoftheconsistencyprotocol.**@returntheconsistencyprotocolerrormessage.*/OptionalgetErrorMsg();如果你不了解Optional,看到这里你可能会有点懵。那我们就来看看Nacos是如何使用Optional的。这是在上述接口的一个实现类PersistentServiceProcessor中实现的:@OverridepublicOptionalgetErrorMsg(){StringerrorMsg;if(hasLeader&&hasError){errorMsg="Theraftpeerisinerror:"+jRaftErrorMsg;}elseif(hasLeader&&!hasError){errorMsg=null;}elseif(!hasLeader&&hasError){errorMsg="Couldnotfindleader!Andtheraftpeerisinerror:"+jRaftErrorMsg;}else{errorMsg="Couldnotfindleader!";}returnOptional.ofNullable(errorMsg);}是根据hasLeader和hasError这两个变量来判断返回errorMsg消息是什么。最后将errorMsg包装成Optional返回。我们看一下getErrorMsg这个方法是怎么调用的:StringerrorMsg;if(ephemeralConsistencyService.getErrorMsg().isPresent()&&persistentConsistencyService.getErrorMsg().isPresent()){errorMsg="'"+ephemeralConsistencyService.getErrorMsg().get()+"'inDistroprotocoland'"+persistentConsistencyService.getErrorMsg()。get()+"'injRaftprotocol";}可以看到在使用的时候只需要调用返回的Optional的isPresent方法判断是否存在,然后调用它的get方法获取即可。到这里你可以回想一下没有Optional是如何实现的。说到这里,你可能对用法还有一些疑惑,没关系,下面开始一步步讲解Option的用法、原理和源码。Optional的数据结构在新事物面前有点吓人。当我们把它拆开,明白了它的实现原理,就没有那么可怕了。查看Optional类的源码,可以看到它有两个成员变量:publicfinalclassOptional{/***Commoninstancefor{@codeempty()}.*/privatestaticfinalOptional>EMPTY=newOptional<>(null);/***Ifnon-null,thevalue;ifnull,表示novalueispresent*/privatefinalTvalue;//...}EMPTY变量表示如果创建了一个空的Optional实例,显然,加载时已经初始化了。而value是用来存放我们业务中实际使用到的对象,比如上面的errorMsg就存放在这里。看到这里,是不是意识到Optional其实是一个容器!是的,把Optional理解为一个容器是对的,然后这个容器为我们封装了存储对象的非空判断和获取API。看到这里,是不是觉得Optional没有那么神秘了呢?是不是很可怕?Java8之所以引入Optional,是为了解决使用对象时写法难看的问题,避免空指针异常。类似如下代码:if(user!=null){Addressaddress=user.getAddress();if(address!=null){Stringprovince=address.getProvince();}}本来是为了封装,为了更优雅代码,这不就是我们这些有抱负的程序员所追求的吗。如何在Optional容器中存储对象那么我们把Optional称为Optional容器,让我们看看如何将对象放在Optional中。看到上面初始化了EMPTY并调用了构造函数,传入了一个null值,那我们是不是也可以这样封装对象呢?它似乎不起作用。我们来看看可选的构造函数:privateOptional(){this.value=null;}privateOptional(Tvalue){this.value=Objects.requireNonNull(value);}存在的两个构造方法都是私有的。好像只能通过Optional提供的其他方法来封装对象,一般有以下几种方式。空方法源码如下://Returnsan{@codeOptional}withthespecifiedpresentnon-nullvalue.publicstaticOptionalof(Tvalue){returnnewOptional<>(value);}简单直接,直接强制空对象。of方法的源码如下:publicstaticTrequireNonNull(Tobj){if(obj==null)thrownewNullPointerException();returnobj;}注释说是为非空值创建一个Optional,并且构造方法中在Objects.requireNonNull方法上面传递非空值来检查:publicstaticTrequireNonNull(Tobj){if(obj==null)thrownewNullPointerException();returnobj;}也就是说,如果值为null,则直接抛出空指针异常。ofNullable方法ofNullable方法源码如下:publicstaticOptionalofNullable(Tvalue){returnvalue==null?empty():of(value);}ofNullable为指定值创建一个Optional,如果指定值为null,则返回一个空的Optional。也就是说,该方法支持对象的null和非null构造。回顾一下:Optional构造函数是私有的,不能被外部调用;empty方法创建一个空的Optional,of方法创建一个非空的Optional,ofNullable结合了两者。有那么容易吗?这时候可能有朋友会问了,相对于ofNullable方法,of方法的存在意义何在?在运行过程中,如果不想隐藏NullPointerException,即如果出现null,则必须立即报告。这时候就用到Of函数。另外,也可以在明确知道该值不会为null的情况下使用。判断对象是否存在上面的Optional中已经放入了对象,那么在获取之前是否需要能够判断存储的对象是否为null呢?isPresent方法的上述问题的答案是:是的。对应的方法是isPresent:publicbooleanisPresent(){returnvalue!=null;}简单明了,相当于封装了obj!=null的判断。如果对象存在,则该方法返回true,否则返回false。isPresent是判断value是否为空,ifPresent是当value不为空时做一些操作:publicvoidifPresent(Consumerconsumer){if(value!=null)consumer.accept(value);}如果Optional实例有值,则为其调用消费者,否则不予处理。Lambda表达式可以直接传递给该方法,代码更加简洁直观。Optionalopt=Optional.of("程序新视野");opt.ifPresent(System.out::println);获取值当我们判断Optional中有值时,我们就可以获取到,就像Nacos中使用的那样,调用get方法:publicTget(){if(value==null){thrownewNoSuchElementException("Novaluepresent");}returnvalue;}显然,如果值为null,这个方法会抛出NoSuchElementException异常。这就是为什么我们需要先调用isPresent方法来判断该值是否存在。这里的设计有点违背初衷。看一下用法示例:Stringname=null;Optionalopt=Optional.ofNullable(name);if(opt.isPresent()){System.out.println(opt.get());}set(orget)Defaultvalue那么,对于上面的值为null的情况,有没有解决的办法呢?我们可以通过设置(或获取)默认值来解决。orElse方法orElse方法:如果有值则返回,否则返回指定的其他值。publicTorElse(Tother){returnvalue!=null?value:other;}可以看出是get方法的增强版。如果get方法的值为null,则直接抛出异常,orElse则不会。如果只是null,就会返回你传入的参数值。使用示例:Optional