Java 8函數語言程式設計模式:不要使用空指標null
空指標並不存在,是我們發明了它,我們現在就擺脫它,好嗎?
下面程式碼練習很簡單:我們需要返回一個格式很好的行,根據他收集的點數為客戶打印合適的折扣:
<b>public</b> String getDiscountLine(Customer customer) { <b>return</b> <font>"Discount%: "</font><font> + getDiscountPercentage(customer.getMemberCard()); } <b>private</b> Integer getDiscountPercentage(MemberCard card) { <b>if</b> (card.getFidelityPoints() >= 100) { <b>return</b> 5; } <b>if</b> (card.getFidelityPoints() >= 50) { <b>return</b> 3; } <b>return</b> <b>null</b>; } </font>
讓我們看看它返回,應該先是60,然後是10點:
System.out.println(discountService.getDiscountLine(<b>new</b> Customer(<b>new</b> MemberCard(60)))); System.out.println(discountService.getDiscountLine(<b>new</b> Customer(<b>new</b> MemberCard(10))));
輸出:
Discount%: 3
Discount%: null
我認為向用戶顯示“null”並不是您每天都想做的事情。顯然,問題是我們連線了一個潛在的null 整數。修復它是很輕鬆的:
<b>public</b> String getDiscountLine(Customer customer) { Integer discount = getDiscountPercentage(customer.getMemberCard()); <b>if</b> (discount != <b>null</b>) { <b>return</b> <font>"Discount%: "</font><font> + discount; } <b>else</b> { <b>return</b> </font><font>""</font><font>; } } </font>
如果沒有折扣,我們返回一個空字串。我們使用空檢查汙染了我們的程式碼。並且,更糟糕的是,我們必須通過窺視getDiscountPercentage()函式來找到我們遇到的問題,以確定它何時可以返回一個null,但是,這種技術不能在大型程式碼庫中擴充套件。相反,您的API應該明確該函式可能不返回任何內容。
<b>import</b> java.util.Optional.*; <b>public</b> String getDiscountLine(Customer customer) { Optional<Integer> discount = getDiscountPercentage(customer.getMemberCard()); <b>if</b> (discount.isPresent()) { <b>return</b> <font>"Discount%: "</font><font> + discount.get(); } <b>else</b> { <b>return</b> </font><font>""</font><font>; } } <b>private</b> Optional<Integer> getDiscountPercentage(MemberCard card) { <b>if</b> (card.getFidelityPoints() >= 100) { <b>return</b> of(5); } <b>if</b> (card.getFidelityPoints() >= 50) { <b>return</b> of(3); } <b>return</b> empty(); } </font>
這裡使用Optional.isPresent() 替代空檢查。
但是,有時候,從我們頭腦中彈出第一個想法並不總是最好的。當你玩的時候Optional,你需要反過來考慮如何使用它。每當你試圖改變魔術盒中的內容時,使用.map(),應用於該魔術盒中,這樣只有存在這個盒裡面的東西才會轉換。
<b>public</b> String getDiscountLine(Customer customer) { <b>return</b> getDiscountPercentage(customer.getMemberCard()) .map(d -> <font>"Discount%: "</font><font> + d).orElse(</font><font>""</font><font>); } </font>
程式碼不僅更簡潔,而且一旦習慣了這種風格,它也更容易閱讀。
下面測試一下沒有MemberCard的Customer :
System.out.println(discountService.getDiscountLine(new Customer()));
輸出:
Exception in thread "main" java.lang.NullPointerException...
我了去!我們經常匆匆忙忙地看看異常突然出現在哪裡。在這裡,它位於getDiscountPercentage()函式的第一行。這是由於我們從未處理過MemberCard引數為null的某些邊界值,我們馬上解決這個問題 - 儘快隱藏那個bug並假裝我們從未見過它:
<b>private</b> Optional<Integer> getDiscountPercentage(MemberCard card) { <b>if</b> (card == <b>null</b>) { <b>return</b> empty(); } <b>if</b> (card.getFidelityPoints() >= 100) { <b>return</b> of(5); } <b>if</b> (card.getFidelityPoints() >= 50) { <b>return</b> of(3); } <b>return</b> empty(); }
,我們再次錯過了一個設計洞察(你看到一個模式嗎?)。我們在這裡快速應用了防禦性程式設計,並針對所有其他無效資料實現保護我們的程式碼。但是,據說最好的防守就是進攻。如果不是在恐懼中守護我們的程式碼,我們會說:“等一下。因此,客戶是否可以沒有會員卡?如果可能存在,Customer.getMemberCard()應該返回一個Optional<MemberCard>
<b>public</b> <b>class</b> Customer { ... <b>public</b> Optional<MemberCard> getMemberCard() { returnofNullable(memberCard); } }
是的,我在這裡觸及神聖的事物。我敢改變領域實體!但是,我相信我更具表現力,因為客戶和會員卡之間的關聯實際上是可選的(1:0..N)
呼叫這段程式碼時我們使用.isPresent()?
<b>private</b> Optional<Integer> getDiscountPercentage(Optional<MemberCard> cardOpt) { <b>if</b> (!cardOpt.isPresent()) { returnempty(); } ...<font><i>// Wait a bit!</i></font><font> </font>
停!
我們一無所獲!我會說這段程式碼變得更加醜陋!但是,有一個乾淨的程式碼規則:不要採用可以為空的引數,因為你需要做的第一件事就是檢查null。在Java 8中,不採用Optional引數作為入參。
我們看到如果有會員卡,會應用getDiscountPercentage()到MemberCard,那麼我們放棄更改getDiscountPercentage(),而是改變getDiscountLine():
<b>public</b> String getDiscountLine(Customer customer) { <b>return</b> customer.getMemberCard() .map(card -> getDiscountPercentage(card)) .map(d -> <font>"Discount%: "</font><font> + d) .orElse(</font><font>""</font><font>); } </font>
輸出可能讓我們感到驚訝:
Discount%: Optional[3]
Discount%: Optional.empty
這是因為第4行的d不再是一個Integer型別,而是一個Optional<Integer>,如果你將滑鼠懸停在第一個上.map()並檢視返回型別,你會自己看到它:Optional<Optional<Integer>>。
在我們的例子中,我們使用.flatMap()而不是.map()去除額外的包裝。
<b>public</b> String getDiscountLine(Customer customer) { <b>return</b> customer.getMemberCard() .flatMap(<b>this</b>::getDiscountPercentage) .map(d -> <font>"Discount%: "</font><font> + d) .orElse(</font><font>""</font><font>); } </font>
因此,只要null存在Java 8中就會給您帶來問題,請不要猶豫,使用Optional 並在可能為空的魔術盒上應用轉換函式。乾淨的程式碼規則變為:不要選擇Optional 引數; 相反,只要你的函式想要向你的呼叫者發出訊號,在某些情況下可能沒有返回值,就返回一個Optional。