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