Java Optional使用的最佳實踐

banq發表於2019-04-05

這是piotr szybicki4年來為了解正確使用Optional型別而努力的結果。
Optional隱藏了可能存在空指標的不確定性,比如:

List<String> numbers= ImmutableList.of("ONE", "TWO", "THREE");

return numbers.stream()
 .filter(number -> "FOUR".equals(number))
 .findAny()
 .toLoweCase();


結果會導致NullPointerExceptions空指標錯誤,而使用if else進行判斷是一種切割器cutter味道:

List<String> numbers= ImmutableList.of("ONE", "TWO", "THREE");
String numberThatImLookingFour = 
numbers.stream()
 .filter(number -> "FOUR".equals(number))
 .findAny();
if(numberThatImLookingFour != null){
 return numberThatImLookingFour.toLowerCase();
}else{
 return "not found";
}


所以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();
if(variable == null){
 1. throw new IllegalStateException("No such variable");
 2. return createVariable();
 3. return "new variable";
} else { 
 ... 
 100 lines of code
 ...
}

重構到:

1. 
Optional<String> variableOpt = fetchOptionalSomeVaraible();
String variable = variableOpt.orElseThrow(() -> new Exeption("")) 
... 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("new variable") 
... 100 lines of code ...


注意,orElse(..)是急切計算,意味著下面程式碼:

Optional<Dog> optionalDog = fetchOptionalDog();
optionalDog
 .map(this::printUserAndReturnUser)
 .orElse(this::printVoidAndReturnUser)



如果值存在則將執行兩個方法,如果值不存在,則僅執行最後一個方法。為了處理這些情況,我們可以使用方法orElseGet(),它將supplier 作為引數,並且是惰性計算的。

#3不要在欄位,方法引數,集合中使用Optional。

下面是將thatField直接賦值給了類欄位:

public void setThatField(Optional <ThatFieldType> thatField){ 
  this.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);
public String dogToString(Dog dog){
 if(dog == null){
   return "DOG'd name is : " + dog.getName();
 } else { 
   return "CAT";
 }
}

重構到:

Optional<Dog> dog = fetchDogIfExists();
String dogsName = dog
 .map(this::convertToDog)
 .orElseGet(this::convertToCat)
public void convertToDog(Dog dog){
   return "DOG'd name is : " + dog.getName();
}
public void convertToCat(){
   return "CAT";
}


Filter是有用的摺疊語法:

Dog dog = fetchDog();
if(optionalDog != null && optionalDog.isBigDog()){
  doBlaBlaBla(optionalDog);
}


上面程式碼可以被重構為:

Optional<Dog> optionalDog = fetchOptionalDog();
optionalDog
 .filter(Dog::isBigDog)
 .ifPresent(this::doBlaBlaBla)



#6不要為了鏈方法而使用optional 。
使用optional 時要注意的一件事是鏈式方法的誘惑。當我們像構建器模式一樣連結方法時,事情可能看起來很漂亮:)。但並不總是等於更具可讀性。所以不要這樣做:

Optional
 .ofNullable(someVariable)
 .ifPresent(this::blablabla)


它對效能不利,對可讀性也不好。我們應儘可能避免使用null引用。


#7使所有表示式成為單行lambda
這是更普遍的規則,我認為也應該應用於流。但這篇文章是關於optional 。使用Optional 重要點是記住等式左邊和右邊一樣重要:

Optional
 .ofNullable(someVariable)
 .map(variable -> {
   try{
      return someREpozitory.findById(variable.getIdOfOtherObject());
   } catch (IOException e){
     LOGGER.error(e); 
     throw new RuntimeException(e); 
   }})
 .filter(variable -> { 
   if(variable.getSomeField1() != null){
     return true;
   } else if(variable.getSomeField2() != null){
     return false;   
   } else { 
     return true;
   }
  })
 .map((variable -> {
   try{
      return jsonMapper.toJson(variable);
   } catch (IOException e){
     LOGGER.error(e); 
     throw new RuntimeException(e); 
   }}))
 .map(String::trim)
 .orElseThrow(() -> new RuntimeException("something went horribly wrong."))


上面那麼冗長程式碼塊可以使用方法替代:

Optional
 .ofNullable(someVariable)
 .map(this::findOtherObject)
 .filter(this::isThisOtherObjectStale)
 .map(this::convertToJson)
 .map(String::trim)
 .orElseThrow(() -> new RuntimeException("something went horribly wrong."));




 

相關文章