Java 8開發的4大頂級技巧

weixin_34320159發表於2019-01-14

我使用Java 8編碼已經有些年頭,既用於新的應用程式,也用來遷移現有的應用,感覺是時候寫一些我發現的非常有用的“最佳實踐”。

我個人並不喜歡“最佳實踐”這個說法,因為它意味著“一刀切”的解決方案,而編碼不可能是這樣的工作方式——我們需要親自去發現什麼樣的解決方案才是有效的。

但是我發現了一些Java 8程式碼中可以幫助我們的一些選擇,讓我們一起來看看吧。

1.Optional

Optional是一個嚴重被低估的功能,並且有潛力刪除很多困擾我們的NullPointerExceptions。這在程式碼邊界中(要麼是正在使用的API,要麼是正在暴露的API)特別有用,因為它允許你和你的呼叫程式碼來推理所期待的東西。

然而,不加思考和設計就應用Optional可能會導致影響大量的類,並可能導致可讀性更差。下面是一些關於如何高效使用Optional的技巧。

Optional應該只用於返回型別

……不是引數,也不是欄位。幸運的是,IntelliJ IDEA的讓你開啟檢查來檢視是是否遵循這些建議。

13898040-7c2544ccbbc8f7f6.png

Optional值應在遇到它們的地方中處理。IntelliJ IDEA的建議會防止程式碼Optional洩漏,所以請記得在你發現Optional的地方處理它,迅速採取行動。

13898040-ab953a50097a1686.png

不應該簡單呼叫get()

Optional的功能是表達這個值可能是空的,並讓你應對這種情況。因此,在對它做任何事情之前一定要檢查是否有一個值。只是簡單得呼叫get()而不先檢查isPresent()在某些時候可能會導致空指標。幸運的是,IntelliJ IDEA也有檢查可以提醒你這一點。

13898040-a7ff903bb85f4202.png

可能有更優雅的方式

結合了get()的isPresent()當然會很贊…

13898040-a41497372c39cd7c.png

……但也有更優雅的解決方案。你可以使用orElse在萬一是空值的情況下給一個替代方案。

13898040-5e8f7ef007bac1db.png

……或者你可以使用orElseGet說明在值為空的情況下呼叫哪個方法。這似乎與上面的例子相同,但supplier方法將只在需要的時候呼叫,因此,如果這是一種昂貴的方法,那麼使用lambda會有更佳效能。推薦閱讀:JDK8新特性之Stream流。

13898040-fc9073c0984bf1bd.png

2.使用Lambda表示式

Lambda表示式是Java 8的主要特點之一。即使你還沒有使用Java 8,你現在可能已經對它們有了基本的瞭解。它們是用Java程式設計的一種新的方式,並且什麼是“最佳實踐”還不明顯。下面是我喜歡遵循的一些指引。

保持簡短

函式式程式設計師與較長的lambda表示式相處會更愉快,但那些淫浸於Java多年的人會發現保持lambda表示式為區區幾行程式碼更容易。你甚至可能更願意將其限制到一行程式碼,並且你可以輕鬆重構較長的表示式為一個方法。

13898040-358f435a7a5aaad9.png

這些甚至可能會成為方法引用。方法引用一開始會覺得有點陌生,但實際上堅持方法引用是有價值的,因為它們在某些情況下有助於可讀性,後面我會討論到這一點。

13898040-e746f95aa8e7a90e.png

明確

型別資訊缺少lambda表示式,所以你可能會覺得包含型別資訊用於引數會很有用。

13898040-cbd5d461ee3d2a52.png

正如你所見,這回變得相當笨拙。所以我更喜歡給引數取一個有用的名字。當然,不管你有沒有這麼做,IntelliJ IDEA可以讓你看到引數得型別資訊。

13898040-e98f6b38f4d41046.png

甚至是lambda所代表的函式式介面:

13898040-f014976bdef7bc11.png

3.針對Lambda表示式設計

我認為lambda表示式有點像泛型——和泛型一起,我們經常使用它們(例如,新增型別資訊到List< >),但最好我們可以設計一種方法或一個具有泛型型別(例如Person< T >)的類。

同樣的,當使用類似於Streams API的東西時,我們會傳遞lambda表示式,但更好的是創造一個需要lambda引數的方法。推薦閱讀:JDK8新特性之Lambda表示式。

但是,如果你發現自己處於這類情況下,下面有一些超棒的技巧。

IntelliJ IDEA可以幫你引進函式式引數

這讓你可以在有人將傳遞一個lambda而非Object的地方建立一個引數。此功能的好處是,它表明,現有函式式介面匹配規格說明。

13898040-a6283a74c62e20b7.png

這會導致…

使用現有的函式式介面

隨著開發人員越來越熟悉Java 8程式碼,我們就能知道當使用如Supplier和 Consumer的介面時,會發生什麼,以及建立一個本地的ErrorMessageCreator(舉個例子)可能會造成混亂,而且浪費。看看這個函式包瞭解一下哪些已經是可用的。推薦閱讀:JDK8新特性之函式式介面。

新增@FunctionalInterface到函式式介面

如果你確實需要建立自己的函式式介面,那麼就這樣用此註釋標記。這似乎沒有太大的作用,但IntelliJ IDEA會告訴你,在你的介面不能匹配用於函式式介面的異常的時候。當你沒有指定要覆蓋的方法時,它會標誌:

13898040-9adea377828e3800.png

當你指定了太多方法的時候,它會標誌:

13898040-4a0b6bdf523298cc.png

並且如果你應用它到一個類而不是介面時,它會警告你:

13898040-26b8d8438171a64d.png

lambda表示式可用於帶有一個單一抽象方法的任何介面,但它們不能用於符合相同標準的抽象類。似乎不合邏輯,但就是這樣。

4.Stream

Stream API是Java 8另一個大特點,並且我認為我們還真的不知道這對我們的編碼方式會產生多大的改變。下面是我發現的一些有用的東西

排隊點操作符

我個人更喜歡排隊我的流操作。當然,你沒有必要這樣,當我發現這樣做對我有幫助:

 一目瞭然地看到我有哪些操作

 除錯更容易(雖然IntelliJ IDEA確實提供了對一行中的任意多個lambda表示式設定斷點的能力,但是拆分到不同的行會變得更簡單)

 當我測試東西的時候註釋操作

 輕鬆插入peek()用於除錯或測試

13898040-d1f24f4bd40ab9a2.png

此外,在我看來,它更整潔。如果我們按照這個模式,在減少程式碼行數方面我們並沒有增加很多。

你可能需要調整格式設定以排列點操作符。

13898040-c0e09f67eaedfd31.png

使用方法引用

是的,確實需要一段時間來適應這個奇怪的語法。但是,如果使用得當,它確實可以增加可讀性。請看:

13898040-22d26bb87a6bba83.png

與(相對)新的Objects類上的輔助方法相比較:

13898040-c81cabb411beffeb.png

後者的程式碼對於哪些值是要儲存的更加明確。當lambda可以被摺疊到方法參考的時候,IntelliJ IDEA通常會讓你知道。

13898040-253e148b86c56829.png

當遍歷一個集合時,在可行的情況下使用Streams API

…或者新的集合方法,如forEach。IntelliJ IDEA給你建議是:

13898040-e7c39dc92b8d7a91.png

一般使用Streams API比迴圈和if語句的組合更加明確。例如:

13898040-c2ee531afd88a306.png

IntelliJ IDEA建議這可重構為:

13898040-0ef91b34ce7b0186.png

我所做的效能測試表明這種重構令人驚訝——並不總是可預測效能是保持不變,改善還是變得更糟。與往常一樣,如果效能在應用程式中是關鍵,那麼在交付一種風格到另一種之前衡量它。

遍歷陣列時使用迴圈

但是,使用Java 8並不一定意味著你必須到處使用流和新的集合方法。IntelliJ IDEA會建議轉換成流,但是,這並不意味著你必須回答“yes”(記得檢查是可以抑制或關閉的)。

特別是,遍歷原始型別的小型陣列幾乎肯定會用,以獲得更好的效能迴圈,很可能(至少對於Java開發人員是新的流)更具可讀性。

13898040-ed0b012536a74635.png

與任何技巧一樣,規則並不是一成不變的,但你應該決定是儘可能地使用Streams API,還是依然對一些操作使用迴圈。總之,要一致。

感興趣的可以自己來我的Java架構群,可以獲取免費的學習資料,群號:855801563 對Java技術,架構技術感興趣的同學,歡迎加群,一起學習,相互討論。

相關文章