Java Optional使用的最佳實踐
這是piotr szybicki 4年來為了解正確使用Optional型別而努力的結果。
Optional隱藏了可能存在空指標的不確定性,比如:
List<String> numbers= ImmutableList.of(<font>"ONE"</font><font>, </font><font>"TWO"</font><font>, </font><font>"THREE"</font><font>); <b>return</b> numbers.stream() .filter(number -> </font><font>"FOUR"</font><font>.equals(number)) .findAny() .toLoweCase(); </font>
結果會導致NullPointerExceptions空指標錯誤,而使用if else進行判斷是一種切割器cutter味道:
List<String> numbers= ImmutableList.of(<font>"ONE"</font><font>, </font><font>"TWO"</font><font>, </font><font>"THREE"</font><font>); String numberThatImLookingFour = numbers.stream() .filter(number -> </font><font>"FOUR"</font><font>.equals(number)) .findAny(); <b>if</b>(numberThatImLookingFour != <b>null</b>){ <b>return</b> numberThatImLookingFour.toLowerCase(); }<b>else</b>{ <b>return</b> </font><font>"not found"</font><font>; } </font>
所以Brian Goetz和Steward Marks(Java語言架構師)聚在一起寫下了以下段落:
我們的目的是為庫方法的返回型別提供一種有限的機制,其中需要一種明確的方式來表示“無結果”,並且對於這樣的方法使用null絕對可能導致錯誤。
因此添加了Optional <T>,這不是一個真正的新概念,歷史可以追溯到Haskell的Maybe monad。突然間,我們獲得了JDK批准的表示可能存在或不存在的值的方式,和往常一樣,在各地使用新功能的開發人員抓狂了。
空引用空指標是無數個錯誤的來源,通常也不能很好地標識“不存在”這個概念。
應該如何處理這個問題的重要部分來自DDD。在有界上下文的入口處我們說:' 你不能通過 ':
傳入領域的所有變數是執行業務邏輯所需的,我們必須減少編寫if(obj == null)檢查判斷的程式碼行數量。我們仍然需要與外部世界(資料庫查詢,REST端點等)進行互動,並根據執行的邏輯互動輸出。如果使用Optional 得當則可以提供幫助。
什麼是Optional?
- 它是box型別,保持對另一個物件的引用。
- 是不可變的,不可序列化的
- 沒有公共建構函式
- 只能是present 或absent
- 通過of(), ofNullable(), empty() 靜態方法建立。
從這個盒子box中如何獲取值?
- get()
- orElse()
- orElseGet()
- orElseThrow()
有一種誘惑是呼叫get()來獲取其中的值。我們都知道普通JavaBean的getter / setters :)。並且期望如果我們呼叫get ...()我們就會得到一些東西。當呼叫普通bean的getter時,你永遠不會得到任何丟擲的異常。但是,如果呼叫在optional上呼叫get方法,並且該選項內部為空時,則會丟擲異常NoSuchElementException。
這些方法應該被稱為getOrThorwSomeHorribleError(),因此第一和第二條規則:
#1不要將null賦給Optional
#2避免使用Optional.get()。如果你不能證明存在可選項,那麼永遠不要呼叫get()。
使用orElse(), orElseGet(), orElseThrow().獲得你的結果。
可以重構以下程式碼:
String variable = fetchSomeVaraible(); <b>if</b>(variable == <b>null</b>){ 1. <b>throw</b> <b>new</b> IllegalStateException(<font>"No such variable"</font><font>); 2. <b>return</b> createVariable(); 3. <b>return</b> </font><font>"new variable"</font><font>; } <b>else</b> { ... 100 lines of code ... } </font>
重構到:
1. Optional<String> variableOpt = fetchOptionalSomeVaraible(); String variable = variableOpt.orElseThrow(() -> <b>new</b> Exeption(<font>""</font><font>)) ... 100 lines of code ... 2. Optional<String> variableOpt = fetchOptionalSomeVaraible(); String variable = variableOpt.orElseGet(() -> createVariable()) ... 100 lines of code ... 3. Optional<String> variableOpt = fetchOptionalSomeVaraible(); String variable = variableOpt.orElse(</font><font>"new variable"</font><font>) ... 100 lines of code ... </font>
注意,orElse(..)是急切計算,意味著下面程式碼:
Optional<Dog> optionalDog = fetchOptionalDog(); optionalDog .map(<b>this</b>::printUserAndReturnUser) .orElse(<b>this</b>::printVoidAndReturnUser)
如果值存在則將執行兩個方法,如果值不存在,則僅執行最後一個方法。為了處理這些情況,我們可以使用方法orElseGet(),它將supplier 作為引數,並且是惰性計算的。
#3不要在欄位,方法引數,集合中使用Optional。
下面是將thatField直接賦值給了類欄位:
<b>public</b> <b>void</b> setThatField(Optional <ThatFieldType> thatField){ <b>this</b>.thatField = thatField; }
.
改為:
setThatField(Optional.ofNullable(thatField));
#4只有每當結果不確定時,使用Optional作為返回型別。。
說實話,這是使用Optional 的唯一好地方。我將複製貼上前面的話:
我們的目的是為庫方法的返回型別提供一種有限的機制,其中需要一種明確的方式來表示“無結果”,並且對於這樣的方法使用null 絕對可能導致錯誤。
#5不要害怕使用map和filter。
有一些值得遵循的一般開發實踐稱為SLA-p:Single Layer of Abstraction字母的第一個大寫。
下面是需要被重構程式碼:
Dog dog = fetchSomeVaraible(); String dogString = dogToString(dog); <b>public</b> String dogToString(Dog dog){ <b>if</b>(dog == <b>null</b>){ <b>return</b> <font>"DOG'd name is : "</font><font> + dog.getName(); } <b>else</b> { <b>return</b> </font><font>"CAT"</font><font>; } } </font>
重構到:
Optional<Dog> dog = fetchDogIfExists(); String dogsName = dog .map(<b>this</b>::convertToDog) .orElseGet(<b>this</b>::convertToCat) <b>public</b> <b>void</b> convertToDog(Dog dog){ <b>return</b> <font>"DOG'd name is : "</font><font> + dog.getName(); } <b>public</b> <b>void</b> convertToCat(){ <b>return</b> </font><font>"CAT"</font><font>; } </font>
Filter是有用的摺疊語法:
Dog dog = fetchDog(); <b>if</b>(optionalDog != <b>null</b> && optionalDog.isBigDog()){ doBlaBlaBla(optionalDog); }
上面程式碼可以被重構為:
Optional<Dog> optionalDog = fetchOptionalDog(); optionalDog .filter(Dog::isBigDog) .ifPresent(<b>this</b>::doBlaBlaBla)
#6不要為了鏈方法而使用optional 。
使用optional 時要注意的一件事是鏈式方法的誘惑。當我們像構建器模式一樣連結方法時,事情可能看起來很漂亮:)。但並不總是等於更具可讀性。所以不要這樣做:
Optional .ofNullable(someVariable) .ifPresent(<b>this</b>::blablabla)
它對效能不利,對可讀性也不好。我們應儘可能避免使用null引用。
#7使所有表示式成為單行lambda
這是更普遍的規則,我認為也應該應用於流。但這篇文章是關於optional 。使用Optional 重要點是記住等式左邊和右邊一樣重要:
Optional .ofNullable(someVariable) .map(variable -> { <b>try</b>{ <b>return</b> someREpozitory.findById(variable.getIdOfOtherObject()); } <b>catch</b> (IOException e){ LOGGER.error(e); <b>throw</b> <b>new</b> RuntimeException(e); }}) .filter(variable -> { <b>if</b>(variable.getSomeField1() != <b>null</b>){ <b>return</b> <b>true</b>; } <b>else</b> <b>if</b>(variable.getSomeField2() != <b>null</b>){ <b>return</b> false; } <b>else</b> { <b>return</b> <b>true</b>; } }) .map((variable -> { <b>try</b>{ <b>return</b> jsonMapper.toJson(variable); } <b>catch</b> (IOException e){ LOGGER.error(e); <b>throw</b> <b>new</b> RuntimeException(e); }})) .map(String::trim) .orElseThrow(() -> <b>new</b> RuntimeException(<font>"something went horribly wrong."</font><font>)) </font>
上面那麼冗長程式碼塊可以使用方法替代:
Optional .ofNullable(someVariable) .map(<b>this</b>::findOtherObject) .filter(<b>this</b>::isThisOtherObjectStale) .map(<b>this</b>::convertToJson) .map(String::trim) .orElseThrow(() -> <b>new</b> RuntimeException(<font>"something went horribly wrong."</font><font>)); </font>