Optional 是個好東西,你會用麼?

Optional 是個好東西,你會用麼?| 原力計劃

作者 | BoCong-Den

封圖 | CSDN下載自東方IC

出品 | CSDN(ID:CSDNnews)

寫在前面

從 Java 8 引入的一個很有趣的特性是 Optional 類。Optional 類主要解決的問題是臭名昭著的空指針異常(PointerException)這個異常就不多說了,肯定是每個 Java 程序員都非常瞭解的異常。Optional 的完整路徑是 java.util.Optional,使用它是為了避免代碼中的 if (obj != ) { } 這樣範式的代碼,可以採用鏈式編程的風格。而且通過 Optional 中提供的 filter 方法可以判斷對象是否符合條件,在符合條件的情況下才會返回,map 方法可以在返回對象前修改對象中的屬性。

Optional 是个好东西,你会用么?| 原力计划

Optional 的用處

本質上,Optional是一個包含有可選值的包裝類,這意味著 Optional 類既可以含有對象也可以為空。我們要知道,Optional 是 Java 實現函數式編程的強勁一步,並且幫助在範式中實現。但是 Optional 的意義顯然不止於此。我們知道,任何訪問對象方法或屬性的調用都可能導致 PointerException,在這裡,我舉個簡單的例子來說明一下

<code>1String result = test.getName.getTime.getNum.getAnswer;
/<code>

在上面的這個代碼中,如果我們需要確保不觸發異常,就得在訪問每一個值之前對其進行明確地檢查,就是使用if else對test等值進行判斷是否為,這很容易就變得冗長,難以維護。為了簡化這個過程,Google公司著名的Guava項目引入了Optional類,Guava通過使用檢查空值的方式來防止代碼汙染,並鼓勵程序員寫更乾淨的代碼。Optional實際上是個容器:它可以保存類型T的值,或者僅僅保存。Optional提供很多有用的方法,這樣我們就不用顯式進行空值檢測。

Optional 是个好东西,你会用么?| 原力计划
Optional 是个好东西,你会用么?| 原力计划

Optional 的構造函數

Optional 的三種構造方式:Optional.of(obj), Optional.ofable(obj) 和明確的 Optional.empty

  • Optional.of(obj):它要求傳入的 obj 不能是 值的, 否則直接報PointerException 異常。

  • Optional.ofable(obj):它以一種智能的,寬容的方式來構造一個 Optional 實例。來者不拒,傳 進到就得到 Optional.empty,非 就調用 Optional.of(obj).

  • Optional.empty:返回一個空的 Optional 對象

Optional 是个好东西,你会用么?| 原力计划

Optional 的常用函數

  • of:為非的值創建一個Optional。of方法通過工廠方法創建Optional類。需要注意的是,創建對象時傳入的參數不能為。如果傳入參數為,則拋出PointerException。因此不經常用。

  • ofable:為指定的值創建一個Optional,如果指定的值為,則返回一個空的Optional。

  • isPresent:如果值存在返回true,否則返回false。

  • ifPresent:如果Optional實例有值則為其調用consumer,否則不做處理

  • get:如果Optional有值則將其返回,否則拋出NoSuchElementException。因此也不經常用。

  • orElse:如果有值則將其返回,否則返回指定的其它值。

  • orElseGet:orElseGet與orElse方法類似,區別在於得到的默認值。orElse方法將傳入的字符串作為默認值,orElseGet方法可以接受Supplier接口的實現用來生成默認值

  • orElseThrow:如果有值則將其返回,否則拋出supplier接口創建的異常。

  • filter:如果有值並且滿足斷言條件返回包含該值的Optional,否則返回空Optional。

  • map:如果有值,則對其執行調用mapping函數得到返回值。如果返回值不為,則創建包含mapping返回值的Optional作為map方法返回值,否則返回空Optional。

  • flatMap:如果有值,為其執行mapping函數返回Optional類型返回值,否則返回空Optional。

Optional 是个好东西,你会用么?| 原力计划

Optional 應該怎樣用

在使用 Optional 的時候需要考慮一些事情,以決定什麼時候怎樣使用它。重要的一點是 Optional 不是 Serializable。因此,它不應該用作類的字段。如果你需要序列化的對象包含 Optional 值,Jackson 庫支持把 Optional 當作普通對象。也就是說,Jackson 會把空對象看作 ,而有值的對象則把其值看作對應域的值。這個功能在 jackson-modules-java8 項目中。Optional 主要用作返回類型,在獲取到這個類型的實例後,如果它有值,你可以取得這個值,否則可以進行一些替代行為。Optional 類可以將其與流或其它返回 Optional 的方法結合,以構建流暢的API。我們來看一個示例,我們不使用Optional寫代碼是這樣的:

<code>1public String getName(User user){
2 if(user == ){
3 return "Unknown";
4 }else return user.name;
5}
/<code>

接著我們來改造一下上面的代碼,使用Optional來改造,我們先來舉一個Optional濫用,沒有達到流暢的鏈式API,反而複雜的例子,如下:

<code>1public String getName(User user){
2 Optional<user> u = Optional.ofable(user);
3 if(!u.isPresent){
4 return "Unknown";
5 }else return u.get.name;
6}
/<user>/<code>

這樣改寫非但不簡潔,而且其操作還是和第一段代碼一樣。無非就是用isPresent方法來替代原先user==。這樣的改寫並不是Optional正確的用法,我們再來改寫一次。

<code>1public String getName(User user){
2 return Optional.ofable(user)
3 .map(u -> u.name)
4 .orElse("Unknown");
5}
/<code>

這樣才是正確使用Optional的姿勢。那麼按照這種思路,我們可以安心的進行鏈式調用,而不是一層層判斷了。當然,我們還可以通過getter方式,對代碼進行進一步縮減(前提是User要有getter方法哦),如下

<code>1String result = Optional.ofable(user)
2 .flatMap(User::getAddress)

3 .flatMap(Address::getCountry)
4 .map(Country::getIsocode)
5 .orElse("default");
/<code>
Optional 是个好东西,你会用么?| 原力计划

Optional 最佳實踐

首先我們先上一張圖,來簡述一下Optional的使用時機:

Optional 是个好东西,你会用么?| 原力计划
  • 避免使用Optional.isPresent來檢查實例是否存在(上面的舉例中提到過),因為這種方式和 != obj沒有區別,這樣用就沒什麼意義了。

  • 避免使用Optional.get方式來獲取實例對象,因為使用前需要使用Optional.isPresent來檢查實例是否存在,否則會出現NoSuchElementException異常問題。所以使用orElse,orElseGet,orElseThrow獲得你的結果

這裡要說明一下的是orElse(…)是急切計算,意味著類似下面代碼:

<code>1Optional optionalDog = fetchOptionalDog;
2optionalDog
3 .map(this::printUserAndReturnUser)
4 .orElse(this::printVoidAndReturnUser)
/<code>

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

  • 避免使用Optional作為類或者實例的屬性,而應該在返回值中用來包裝返回實例對象。

  • 避免使用Optional作為方法的參數,原因同3。

  • 不要將賦給Optional

  • 只有每當結果不確定時,使用Optional作為返回類型,從某種意義上講,這是使用Optional的唯一好地方,用java官方的話講就是:我們的目的是為庫方法的返回類型提供一種有限的機制,其中需要一種明確的方式來表示“無結果”,並且對於這樣的方法使用 絕對可能導致錯誤。

  • 不要害怕使用map和filter,有一些值得遵循的一般開發實踐稱為SLA-p:Single Layer of Abstraction字母的第一個大寫。下面是需要被重構代碼到重構的代碼


示例一

<code> 1Dog dog = fetchSomeVaraible;
2String dogString = dogToString(dog);
3public String dogToString(Dog dog){
4 if(dog == ){
5 return "DOG'd name is : " + dog.getName;
6 } else {
7 return "CAT";
8 }
9}

10//上面代碼重構到下面代碼
11Optional dog = fetchDogIfExists;
12String dogsName = dog
13 .map(this::convertToDog)
14 .orElseGet(this::convertToCat)
15
16public void convertToDog(Dog dog){
17 return "DOG'd name is : " + dog.getName;
18}
19
20public void convertToCat{
21 return "CAT";
22}
/<code>

示例二

<code>1Dog dog = fetchDog;
2if(optionalDog != && optionalDog.isBigDog){
3 doBlaBlaBla(optionalDog);
4}
5//上面代碼重構到下面代碼
6Optional optionalDog = fetchOptionalDog;
7optionalDog
8 .filter(Dog::isBigDog)
9 .ifPresent(this::doBlaBlaBla)
/<code>
  • 不要為了鏈方法而使用optional。使用optional 時要注意的一件事是鏈式方法的誘惑。當我們像構建器模式一樣鏈接方法時,事情可能看起來很漂亮。但並不總是等於更具可讀性。所以不要這樣做,它對性能不利,對可讀性也不好。我們應儘可能避免使用引用。

<code>1Dog dog = fetchDog;
2if(optionalDog != && optionalDog.isBigDog){
3 doBlaBlaBla(optionalDog);
4}
5//上面代碼重構到下面代碼
6Optional optionalDog = fetchOptionalDog;
7optionalDog
8 .filter(Dog::isBigDog)
9 .ifPresent(this::doBlaBlaBla)
/<code>
  • 使所有表達式成為單行lambda。這是更普遍的規則,我認為也應該應用於流。但這篇文章是關於optional 。使用Optional 重要點是記住等式左邊和右邊一樣重要,這裡舉個例子

<code> 1Optional
2 .ofable(someVariable)
3 .map(variable -> {
4 try{
5 return someREpozitory.findById(variable.getIdOfOtherObject);
6 } catch (IOException e){
7 LOGGER.error(e);
8 throw new RuntimeException(e);
9 }})
10 .filter(variable -> {
11 if(variable.getSomeField1 != ){
12 return true;
13 } else if(variable.getSomeField2 != ){
14 return false;
15 } else {
16 return true;
17 }
18 })
19 .map((variable -> {
20 try{
21 return jsonMapper.toJson(variable);

22 } catch (IOException e){
23 LOGGER.error(e);
24 throw new RuntimeException(e);
25 }}))
26 .map(String::trim)
27 .orElseThrow( -> new RuntimeException("something went horribly wrong."))
/<code>

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

<code>1Optional
2 .ofable(someVariable)
3 .map(this::findOtherObject)
4 .filter(this::isThisOtherObjectStale)
5 .map(this::convertToJson)
6 .map(String::trim)
7 .orElseThrow( -> new RuntimeException("something went horribly wrong."));
/<code>

原文鏈接:

https://blog.csdn.net/DBC_121/article/details/104984093

CSDN VIP會員卡新增權益啦!!!

數百本電子書現在免費閱讀啦!下載全站資源,免費觀看千門課程。每日學習僅需0.8元。

Optional 是个好东西,你会用么?| 原力计划


分享到:


相關文章: