Nacos中已經(jīng)有Optional使用案例了,是時候慎重對待這一語法了
本文轉(zhuǎn)載自微信公眾號「程序新視界」,作者二師兄。轉(zhuǎn)載本文請聯(lián)系程序新視界公眾號。
前言
Java 8提供了很多新特性,但很多朋友對此并不重視,依舊采用老的寫法。最近個人在大量閱讀開源框架的源碼,發(fā)現(xiàn)Java 8的很多API已經(jīng)被頻繁的使用了。
以Nacos框架為例,已經(jīng)有很典型的Optional使用案例了,而且場景把握的非常好。如果此時你還沒意識到要學(xué)習(xí)了解一下,以后看源代碼可能都有些費勁了。
今天這篇文章我們就基于Nacos中對Optional的使用作為案例,來深入講解一下Optional的使用。老規(guī)矩,源碼、案例、實戰(zhàn),一樣不少。
Nacos中的Optional使用
在Nacos中有這樣一個接口ConsistencyService,用來定義一致性服務(wù)的,其中的一個方法返回的類型便是Optional:
- /**
- * Get the error message of the consistency protocol.
- *
- * @return the consistency protocol error message.
- */
- Optional<String> getErrorMsg();
如果你對Optional不了解,看到這里可能就會有點蒙。那我們來看看Nacos是怎么使用Optional的。在上述接口的一個實現(xiàn)類PersistentServiceProcessor中是如此實現(xiàn)的:
- @Override
- public Optional<String> getErrorMsg() {
- String errorMsg;
- if (hasLeader && hasError) {
- errorMsg = "The raft peer is in error: " + jRaftErrorMsg;
- } else if (hasLeader && !hasError) {
- errorMsg = null;
- } else if (!hasLeader && hasError) {
- errorMsg = "Could not find leader! And the raft peer is in error: " + jRaftErrorMsg;
- } else {
- errorMsg = "Could not find leader!";
- }
- return Optional.ofNullable(errorMsg);
- }
也就是根據(jù)hasLeader和hasError兩個變量來確定返回的errorMsg信息是什么。最后將errorMsg封裝到Optional中進(jìn)行返回。
下面再看看方法getErrorMsg是如何被調(diào)用的:
- String errorMsg;
- if (ephemeralConsistencyService.getErrorMsg().isPresent()
- && persistentConsistencyService.getErrorMsg().isPresent()) {
- errorMsg = "'" + ephemeralConsistencyService.getErrorMsg().get() + "' in Distro protocol and '"
- + persistentConsistencyService.getErrorMsg().get() + "' in jRaft protocol";
- }
可以看到在使用時只用先調(diào)用返回的Optional的isPresent方法判斷是否存在,再調(diào)用其get方法獲取即可。此時你可以回想一下如果不用Optional該如何實現(xiàn)。
到此,你可能有所疑惑用法,沒關(guān)系,下面我們就開始逐步講解Option的使用、原理和源碼。
Optional的數(shù)據(jù)結(jié)構(gòu)
面對新生事物我們都會有些許畏懼,當(dāng)我們庖丁解牛似的將其拆分之后,了解其實現(xiàn)原理,就沒那么恐怖了。
查看Optional類的源碼,可以看到它有兩個成員變量:
- public final class Optional<T> {
- /**
- * Common instance for {@code empty()}.
- */
- private static final Optional<?> EMPTY = new Optional<>(null);
- /**
- * If non-null, the value; if null, indicates no value is present
- */
- private final T value;
- // ...
- }
其中EMPTY變量表示的是如果創(chuàng)建一個空的Optional實例,很顯然,在加載時已經(jīng)初始化了。而value是用來存儲我們業(yè)務(wù)中真正使用的對象,比如上面的errorMsg就是存儲在這里。
看到這里你是否意識到Optional其實就一個容器啊!對的,將Optional理解為容器就對了,然后這個容器呢,為我們封裝了存儲對象的非空判斷和獲取的API。
看到這里,是不是感覺Optional并沒那么神秘了?是不是也沒那么恐懼了?
而Java 8之所以引入Optional也是為了解決對象使用時為避免空指針異常的丑陋寫法問題。類似如下代碼:
- if( user != null){
- Address address = user.getAddress();
- if(address != null){
- String province = address.getProvince();
- }
- }
原來是為了封裝,原來是為了更優(yōu)雅的代碼,這不正是我們有志向的程序員所追求的么。
如何將對象存入Optional容器中
這么我們就姑且稱Optional為Optional容器了,下面就看看如何將對象放入Optional當(dāng)中。
看到上面的EMPTY初始化時調(diào)用了構(gòu)造方法,傳入null值,我們是否也可以這樣來封裝對象?好像不行,來看一下Optional的構(gòu)造方法:
- private Optional() {
- this.value = null;
- }
- private Optional(T value) {
- this.value = Objects.requireNonNull(value);
- }
存在的兩個構(gòu)造方法都是private的,看來只能通過Optional提供的其他方法來封裝對象了,通常有以下方式。
empty方法
empty方法源碼如下:
- // Returns an {@code Optional} with the specified present non-null value.
- public static <T> Optional<T> of(T value) {
- return new Optional<>(value);
- }
簡單直接,直接強轉(zhuǎn)EMPTY對象。
of方法
of方法源碼如下:
- public static <T> T requireNonNull(T obj) {
- if (obj == null)
- throw new NullPointerException();
- return obj;
- }
注釋上說是為非null的值創(chuàng)建一個Optional,而非null的是通過上面構(gòu)造方法中的Objects.requireNonNull方法來檢查的:
- public static <T> T requireNonNull(T obj) {
- if (obj == null)
- throw new NullPointerException();
- return obj;
- }
也就是說如果值為null,則直接拋空指針異常。
ofNullable方法
ofNullable方法源碼如下:
- public static <T> Optional<T> ofNullable(T value) {
- return value == null ? empty() : of(value);
- }
ofNullable為指定的值創(chuàng)建一個Optional,如果指定的值為null,則返回一個空的Optional。也就是說此方法支持對象的null與非null構(gòu)造。
回顧一下:Optional構(gòu)造方法私有,不能被外部調(diào)用;empty方法創(chuàng)建空的Optional、of方法創(chuàng)建非空的Optional、ofNullable將兩者結(jié)合。是不是so easy?
此時,有朋友可能會問,相對于ofNullable方法,of方法存在的意義是什么?在運行過程中,如果不想隱藏NullPointerException,就是說如果出現(xiàn)null則要立即報告,這時就用Of函數(shù)。另外就是已經(jīng)明確知道value不會為null的時候也可以使用。
判斷對象是否存在
上面已經(jīng)將對象放入Optional了,那么在獲取之前是否需要能判斷一下存放的對象是否為null呢?
isPresent方法
上述問題,答案是:可以的。對應(yīng)的方法就是isPresent:
- public boolean isPresent() {
- return value != null;
- }
實現(xiàn)簡單直白,相當(dāng)于將obj != null的判斷進(jìn)行了封裝。該對象如果存在,方法返回true,否則返回false。
isPresent即判斷value值是否為空,而ifPresent就是在value值不為空時,做一些操作:
- public void ifPresent(Consumer<? super T> consumer) {
- if (value != null)
- consumer.accept(value);
- }
如果Optional實例有值則為其調(diào)用consumer,否則不做處理??梢灾苯訉ambda表達(dá)式傳遞給該方法,代碼更加簡潔、直觀。
- Optional<String> opt = Optional.of("程序新視界");
- opt.ifPresent(System.out::println);
獲取值
當(dāng)我們判斷Optional中有值時便可以進(jìn)行獲取了,像Nacos中使用的那樣,調(diào)用get方法:
- public T get() {
- if (value == null) {
- throw new NoSuchElementException("No value present");
- }
- return value;
- }
很顯然,如果value值為null,則該方法會拋出NoSuchElementException異常。這也是為什么我們在使用時要先調(diào)用isPresent方法來判斷一下value值是否存在了。此處的設(shè)計稍微與初衷相悖。
看一下使用示例:
- String name = null;
- Optional<String> opt = Optional.ofNullable(name);
- if(opt.isPresent()){
- System.out.println(opt.get());
- }
設(shè)置(或獲取)默認(rèn)值
那么,針對上述value為null的情況是否有解決方案呢?我們可以配合設(shè)置(或獲取)默認(rèn)值來解決。
orElse方法
orElse方法:如果有值則將其返回,否則返回指定的其它值。
- public T orElse(T other) {
- return value != null ? value : other;
- }
可以看到是get方法的加強版,get方法如果值為null直接拋異常,orElse則不,如果只為null,返回你傳入進(jìn)來的參數(shù)值。
使用示例:
- Optional<Object> o1 = Optional.ofNullable(null);
- // 輸出orElse指定值
- System.out.println(o1.orElse("程序新視界"));
orElseGet方法
orElseGet:orElseGet與orElse方法類似,區(qū)別在于得到的默認(rèn)值。orElse方法將傳入的對象作為默認(rèn)值,orElseGet方法可以接受Supplier接口的實現(xiàn)用來生成默認(rèn)值:
- public T orElseGet(Supplier<? extends T> other) {
- return value != null ? value : other.get();
- }
當(dāng)value為null時orElse直接返回傳入值,orElseGet返回Supplier實現(xiàn)類中定義的值。
- String name = null;
- String newName = Optional.ofNullable(name).orElseGet(()->"程序新視界");
- System.out.println(newName); // 輸出:程序新視界
其實上面的示例可以直接優(yōu)化為orElse,因為Supplier接口的實現(xiàn)依舊是直接返回輸入值。
orElseThrow方法
orElseThrow:如果有值則將其返回,否則拋出Supplier接口創(chuàng)建的異常。
- public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
- if (value != null) {
- return value;
- } else {
- throw exceptionSupplier.get();
- }
- }
使用示例:
- Optional<Object> o = Optional.ofNullable(null);
- try {
- o.orElseThrow(() -> new Exception("異常"));
- } catch (Exception e) {
- System.out.println(e.getMessage());
- }
學(xué)完上述內(nèi)容,基本上已經(jīng)掌握了Optional百分之八十的功能了。同時,還有兩個相對高級點的功能:過濾值和轉(zhuǎn)換值。
filter方法過濾值
Optional中的值我們可以通過上面講的到方法進(jìn)行獲取,但在某些場景下,我們還需要判斷一下獲得的值是否符合條件。笨辦法時,獲取值之后,自己再進(jìn)行檢查判斷。
當(dāng)然,也可以通過Optional提供的filter來進(jìn)行取出前的過濾:
- public Optional<T> filter(Predicate<? super T> predicate) {
- Objects.requireNonNull(predicate);
- if (!isPresent())
- return this;
- else
- return predicate.test(value) ? this : empty();
- }
filter方法的參數(shù)類型為Predicate類型,可以將Lambda表達(dá)式傳遞給該方法作為條件,如果表達(dá)式的結(jié)果為false,則返回一個EMPTY的Optional對象,否則返回經(jīng)過過濾的Optional對象。
使用示例:
- Optional<String> opt = Optional.of("程序新視界");
- Optional<String> afterFilter = opt.filter(name -> name.length() > 4);
- System.out.println(afterFilter.orElse(""));
map方法轉(zhuǎn)換值
與filter方法類似,當(dāng)我們將值從Optional中取出之后,還進(jìn)行一步轉(zhuǎn)換,比如改為大寫或返回長度等操作。當(dāng)然可以用笨辦法取出之后,進(jìn)行處理。
這里,Optional為我們提供了map方法,可以在取出之前就進(jìn)行操作:
- public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
- Objects.requireNonNull(mapper);
- if (!isPresent())
- return empty();
- else {
- return Optional.ofNullable(mapper.apply(value));
- }
- }
map方法的參數(shù)類型為Function,會調(diào)用Function的apply方法對對Optional中的值進(jìn)行處理。如果Optional中的值本身就為null,則返回空,否則返回處理過后的值。
示例:
- Optional<String> opt = Optional.of("程序新視界");
- Optional<Integer> intOpt = opt.map(String::length);
- System.out.println(intOpt.orElse(0));
與map方法有這類似功能的方法為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));
- }
- }
可以看出,它與map方法的實現(xiàn)非常像,不同的是傳入的參數(shù)類型,map函數(shù)所接受的入?yún)㈩愋蜑镕unction,而flapMap的入?yún)㈩愋蜑镕unction>。
flapMap示例如下:
- Optional<String> opt = Optional.of("程序新視界");
- Optional<Integer> intOpt = opt.flatMap(name ->Optional.of(name.length()));
- System.out.println(intOpt.orElse(0));
對照map的示例,可以看出在flatMap中對結(jié)果進(jìn)行了一次Optional#of的操作。
小結(jié)
本文我們從Nacos中使用Optional的使用出發(fā),逐步剖析了Optional的源碼、原理和使用。此時再回頭看最初的示例是不是已經(jīng)豁然開朗了?
關(guān)于Optional的學(xué)習(xí)其實把握住本質(zhì)就可以了:Optional本質(zhì)上是一個對象的容器,將對象存入其中之后,可以幫我們做一些非空判斷、取值、過濾、轉(zhuǎn)換等操作。
理解了本質(zhì),如果哪個API的使用不確定,看一下源碼就可以了。此時,可以愉快的繼續(xù)看源碼了~