大多數開發者對 Guava 的認識,是從其中對Java 標準 API 中 Java Collections 框架功能的增強或補充開始,那是 guava-libraries 中的一部份,也就是 Guava Collections,在這部份,Java Collections 框架參與者之一 Joshua Bloch 也曾對其提出相關建議。然而 guava-libraries 不僅僅是 Collections 的功能加強版,它還包括了許多進階特性,甚至有不少設計是借鏡了函數式程式設計的概念,像是不可變動結構、避免
null
等,對不少 Java 傳統開發者而言,這些概念都是新穎而陌生的,也因此無法對其作更進一步的善用。在 guava-libraries 的 Wiki 上,一開始就談到了 Using/avoiding null,上頭一開始引用了 Java Collections 群集框架及 JSR166 參與者之一 Doug Lea 的話:
"Null sucks."
接著又引用了圖靈獎得主、快速排序發明者 Tony Hoare,在 QCon London 2009 主講《Null References: The Billion Dollar Mistake》場次時的一段話:"I call it my billion-dollar mistake."
null
的問題在於含糊而不明確,引發的各種問題從Java 開發者經常在與 NullPointerException
奮戰可見一般,我在 補救 null 的策略 中談過:
null
的最根本問題在於語意含糊不清,雖然就字面來說,null
可以是「不存在」、「沒有」、「無」或「空」的概念,因此在應用時,總是令人感到模稜兩可,也就讓開發者有了各自解釋的空間,當開發者想到「嘿!這邊可以沒有東西...」就直接放個 null
,或者是想到「嗯!沒什麼東西可以傳回...」,就不假思索地傳回個 null
,然後使用者就總是忘了檢查 null
,引發各種可能的錯誤。
null
的根本問題在於含糊而不明確,要避免使用 null
的方式,就是確認過去使用 null
的時機與目的,並使用明確的語義。在過去使用 null
的情況中,開發者於方法中傳回 null
,通常代表著客戶端必須檢查是否為 null
,並在 null
的情況下使用預設值,以便後續程式繼續執行。舉個例子來說,如果原先有個 getNickName
方法可傳回 String
,而客戶端有以下的執行流程:
String nickName = getNickName("Duke");
if(nickName == null) {
nickName = "CodeData User";
}
out.println(nickName);
如果客戶端忘了檢查 null
,那麼就會直接顯示 null,在這個簡單的例子中並不會怎樣,只是顯示結果令人困惑罷了,但如果後續的執行流程牽涉到至關重要的結果,程式快樂地繼續執行下去,錯誤可能到最後才會呈現發生。那麼可將
getNickName
修改使一定會傳回 Optional<String>
(在 com.google.common.base
套件)實例,但絕不要傳回 null
。Optional
的語義是它可能包含也可能不包括值,如果你呼叫直接呼叫它的 get
方法:
String nickName = getNickName("Duke").get();
out.println(nickName);
在 Optional
沒有包含值的情況下,就會直接拋出 IllegalStateException
,這實現了速錯(Fail fast)的概念,這讓開發者可以立即發現錯誤,並瞭解到必須作些檢查,可能的方式之一像是:
Optional<String> nick = getNickName("Duke");
String nickName = nick.isPresent() ? nick.get() : "CodeData User";
out.println(nickName);
不過這看來有點囉嗦,一個比較好的方式可以是:
String nickName = getNickName("Duke").or("CodeData User");
out.println(nickName);
在 getNickName
方法內部,原先如果是這樣傳回值:
return rs.next() ? rs.getString("nickname") : null;
則可以使用 Optional
改為:
return rs.next() ? Optional.of(rs.getString("nickname")) : Optional.absent();
Optional.of
方法用來建立 Optional
物件包裏 of
的傳入值,而 Optional.absent
建立的 Optional
實例不會包裹任何值,也就是如果你直接呼叫後者建立的 Optional
物件上 get
方法,就會拋出例外。在有值的情況下使用 Optional.of
,在原本會傳回 null
的情況下使用 Optional.absent。
當然,過去許多程式庫中使用了不少
null
,這些程式庫無法說改就改,Guava 提供了一些銜接程式庫中 null
的方法。例如,如果原先的 getNickName
是你無法修改的,那麼可以這麼修改客戶端:
String nickName = Optional.fromNullable(getNickName("Duke")).or("CoDeData User");
out.println(nickName);
Optional.fromNullable
在傳入值為 null
的情況下,傳回的 Optional
實例呼叫其 get
方法,就會拋出錯誤。
在 Guava 上還有一些方法,是針對字串而設定,最主要就是讓語義清楚。因為過去對於字串沒有值,各開發者會有各自的表示,因而造成同一個程式中,有可能某些開發者認為字串沒有值時,應該傳回 null
,有的開發者則認為空字串才是表示字串沒有值。無論如何,在需要統一的場合中,可以使用 Strings.emptyToNull
(Strings
是在 com.google.common.base
套件)、Strings.nullToEmpty
方法。如果程式中想統一字串沒有值時,必須是 null
,那麼可在原本採用空字串的地方呼叫 Strings.emptyToNull("")
,反之,若想統一字串沒有值時一律以空字串表示,那麼就使用 Strings.nullToEmpty(null)
。現在,你可以進一步看看 Guava 的 Wiki 上 Using/avoiding null 說的是怎麼一回事了。所以你知道嗎?JDK8 中也會有個
Optional
!