Guava學習筆記:Optional優雅的使用null
在我們學習和使用Guava的Optional之前,我們需要來了解一下Java中null。因為,只有我們深入的了解了null的相關知識,我們才能更加深入體會領悟到Guava的Optional設計和使用上的優雅和簡單。
null代表不確定的對象:
Java中,null是一個關鍵字,用來標識一個不確定的對象。因此可以將null賦給引用類型變量,但不可以將null賦給基本類型變量。
Java中,變量的使用都遵循一個原則:先定義,並且初始化後,才可以使用。例如如下代碼中,我們不能定義int age後,不給age指定值,就去打印age的值。這條對對於引用類型變量也是適用的(String name也同樣適用),在編譯的時候就會提示為初始化。
public class NullTest { public static void testNull(){ int age; System.out.println("user age:"+age); long money; money=10L; System.out.println("user money"+money); String name; System.out.println("user name:"+name); } }
在Java中,Java默認給變量賦值:在定義變量的時候,如果定義後沒有給變量賦值,則Java在運行時會自動給變量賦值。賦值原則是整數類型int、byte、short、long的自動賦值為0,帶小數點的float、double自動賦值為0.0,boolean的自動賦值為false,其他各供引用類型變量自動賦值為null。上面代碼會變為如下可運行代碼:
public class NullTest { public static void testNull(){ int age = 0; System.out.println("user Age:"+age); long money; money=10L; System.out.println("user money"+money); String name = null; System.out.println("user name:"+name); } }
null本身不是對象,也不是Objcet的實例:
null只是一個關鍵字,用來標識一個不確定的對象,他既不是對象,也不是Objcet對象的實例。下面我們通過代碼確定一下null是不是Object對象實例:
public class NullTest { public static void main(String[] args) { testNullObject(); } public static void testNullObject() { if (null instanceof java.lang.Object) { System.out.println("null屬於java.lang.Object類型"); } else { System.out.println("null不屬於java.lang.Object類型"); } } }
運行上面代碼,輸出:null不屬於java.lang.Object類型,可見,null對象不是Object對象的實例。
null對象的使用:
1.常見使用場景:
有時候,我們定義一個引用類型變量,在剛開始的時候,無法給出一個確定的值,但是不指定值,程序可能會在try語句塊中初始化值。這時候,我們下面使用變量的時候就會報錯。這時候,可以先給變量指定一個null值,問題就解決了。例如:
Connection conn = null; try { conn = DriverManager.getConnection("url", "user", "password"); } catch (SQLException e) { e.printStackTrace(); } String catalog = conn.getCatalog();
如果剛開始的時候不指定conn = null,則最後一句就會報錯。
2.容器類型與null:
List:允許重復元素,可以加入任意多個null。
Set:不允許重復元素,最多可以加入一個null。
Map:Map的key最多可以加入一個null,value字段沒有限制。
數組:基本類型數組,定義後,如果不給定初始值,則java運行時會自動給定值。引用類型數組,不給定初始值,則所有的元素值為null。
3.null的其他作用
1>、判斷一個引用類型數據是否null。 用==來判斷。
2>、釋放內存,讓一個非null的引用類型變量指向null。這樣這個對象就不再被任何對象應用了。等待JVM垃圾回收機制去回收。
4.null的使用建議:
1>. 在Set或者Map中使用null作為鍵值指向的value,千萬別這麽用。很顯然,在Set和Map的查詢操作中,將null作為特殊的例子可以使查詢結果更淺顯易懂。
2>. 在Map中包含value是null值的鍵值對,你應該把這種鍵值對移出map,使用一個獨立的Set來包含所有null或者非null的鍵。很容易混淆的是,一個Map是不是包含value是 null的key,還是說這個Map中沒有這樣的鍵值對。最好的辦法就是把這類key值分立開來,並且好好想想到底一個value是null的鍵值對對於你的程序來說到底意味著什麽。
3>. 在列表中使用null,並且這個列表的數據是稀疏的,或許你最好應該使用一個Map<Integer,E>字典來代替這個列表。因為字典更高效,並且也許更加精確的符合你潛意識裏對程序的需求。
4>. 想象一下如果有一種自然的“空對象”可以使用,比方說對於枚舉類型,添加一個枚舉常數實例,這個實例用來表示你想用null值所表示的情形。比如:Java.math.RoundingMode有一個常數實例UNNECESSARY來表示“不需要四舍五入”,任何精度計算的方法若傳以RoundingMode.UNNECESSARY為參數來計算,必然拋出一個異常來表示不需要舍取精度。
5.問題和困惑:
首先,對於null的隨意使用會一系列難以預料的問題。通過對大量代碼的研究和分析,我們發現大概95%以上的集合類默認並不接受null值,如果有null值將被放入集合中,代碼會立刻中斷並報錯而不是默認存儲null值,對於開發來說,這樣能夠更加容易的定位程序出錯的地方。
另外,null值是一種令人不滿的模糊含義。有的時候會產生二義性,這時候我們就很難搞清楚具體的意思,如果程序返回一個null值,其代表的含義到底是什麽,例如:Map.get(key)若返回value值為null,其代表的含義可能是該鍵指向的value值是null,亦或者該鍵在map中並不存在。null值可以表示失敗,可以表示成功,幾乎可以表示任何情況。用其它一些值(而不是null值)可以讓你的代碼表述的含義更清晰。
反過來說,使用null值在有些情況下是一種正確的選擇,因為從內存消耗和效率方面考慮,使用null更加廉價,而且在對象數組中出現null也是不可避免的。但是在程序代碼中,比方說在函數庫中,null值的使用會變成導致誤解的元兇,也會導致一些莫名的,模糊的,很難修正的問題。就像上述map的例子,字典返回null可以代表的是該鍵指向的值存在且為空,或者也可以代表字典中沒有這個鍵。關鍵在於,null值不能指明到底null代表了什麽含義。
Guava的Optional:
大多數情況下程序員使用null是為了表示某種不存在的意思,也許應該有一個value,但是這個value是空或者這個value找不到。比方說,在用不存在的key值從map中取 value,Map.get返回null表示沒有該map中不包含這個key。
若T類型數據可以為null,Optional<T>是用來以非空值替代T數據類型的一種方法。一個Optional對象可以包含一個非空的T引用(這種情況下我們稱之為“存在的”)或者不包含任何東西(這種情況下我們稱之為“空缺的”)。但Optional從來不會包含對null值的引用。
import com.google.common.base.Optional; public class OptionalTest { public void testOptional() throws Exception { Optional<Integer> possible=Optional.of(6); if(possible.isPresent()){ System.out.println("possible isPresent:"+possible.isPresent()); System.out.println("possible value:"+possible.get()); } } }
由於這些原因,Guava庫設計了Optional來解決null的問題。許多Guava的工具被設計成如果有null值存在即刻報錯而不是只要上下文接受處理null值就默認使用null值繼續運行。而且,Guava提供了Optional等一些工具讓你在不得不使用null值的時候,可以更加簡便的使用null並幫助你避免直接使用null。
Optional<T>的最常用價值在於,例如,假設一個方法返回某一個數據類型,調用這個方法的代碼來根據這個方法的返回值來做下一步的動作,若該方法可以返回一個null值表示成功,或者表示失敗,在這裏看來都是意義含糊的,所以使用Optional<T>作為返回值,則後續代碼可以通過isPresent()來判斷是否返回了期望的值(原本期望返回null或者返回不為null,其意義不清晰),並且可以使用get()來獲得實際的返回值。
Optional方法說明和使用實例:
1.常用靜態方法:
Optional.of(T):獲得一個Optional對象,其內部包含了一個非null的T數據類型實例,若T=null,則立刻報錯。
Optional.absent():獲得一個Optional對象,其內部包含了空值
Optional.fromNullable(T):將一個T的實例轉換為Optional對象,T的實例可以不為空,也可以為空[Optional.fromNullable(null),和Optional.absent()等價。
使用實例如下:
import com.google.common.base.Optional; public class OptionalTest { @Test public void testOptional() throws Exception { Optional<Integer> possible=Optional.of(6); Optional<Integer> absentOpt=Optional.absent(); Optional<Integer> NullableOpt=Optional.fromNullable(null); Optional<Integer> NoNullableOpt=Optional.fromNullable(10); if(possible.isPresent()){ System.out.println("possible isPresent:"+possible.isPresent()); System.out.println("possible value:"+possible.get()); } if(absentOpt.isPresent()){ System.out.println("absentOpt isPresent:"+absentOpt.isPresent()); ; } if(NullableOpt.isPresent()){ System.out.println("fromNullableOpt isPresent:"+NullableOpt.isPresent()); ; } if(NoNullableOpt.isPresent()){ System.out.println("NoNullableOpt isPresent:"+NoNullableOpt.isPresent()); ; } } }
2.實例方法:
1>. boolean isPresent():如果Optional包含的T實例不為null,則返回true;若T實例為null,返回false
2>. T get():返回Optional包含的T實例,該T實例必須不為空;否則,對包含null的Optional實例調用get()會拋出一個IllegalStateException異常
3>. T or(T):若Optional實例中包含了傳入的T的相同實例,返回Optional包含的該T實例,否則返回輸入的T實例作為默認值
4>. T orNull():返回Optional實例中包含的非空T實例,如果Optional中包含的是空值,返回null,逆操作是fromNullable()
5>. Set<T> asSet():返回一個不可修改的Set,該Set中包含Optional實例中包含的所有非空存在的T實例,且在該Set中,每個T實例都是單態,如果Optional中沒有非空存在的T實例,返回的將是一個空的不可修改的Set。
使用實例如下:
import java.util.Set; import com.google.common.base.Optional; public class OptionalTest { public void testMethodReturn() { Optional<Long> value = method(); if(value.isPresent()==true){ System.out.println("獲得返回值: " + value.get()); }else{ System.out.println("獲得返回值: " + value.or(-12L)); } System.out.println("獲得返回值 orNull: " + value.orNull()); Optional<Long> valueNoNull = methodNoNull(); if(valueNoNull.isPresent()==true){ Set<Long> set=valueNoNull.asSet(); System.out.println("獲得返回值 set 的 size : " + set.size()); System.out.println("獲得返回值: " + valueNoNull.get()); }else{ System.out.println("獲得返回值: " + valueNoNull.or(-12L)); } System.out.println("獲得返回值 orNull: " + valueNoNull.orNull()); } private Optional<Long> method() { return Optional.fromNullable(null); } private Optional<Long> methodNoNull() { return Optional.fromNullable(15L); } }
輸出結果:
獲得返回值: -12 獲得返回值 orNull: null 獲得返回值 set 的 size : 1 獲得返回值: 15 獲得返回值 orNull: 15
Optional除了給null值命名所帶來的代碼可閱讀性的提高,最大的好處莫過於Optional是傻瓜式的。Optional對象的使用強迫你去積極的思考這樣一種情況,如果你想讓你的程序返回null值,這null值代表的含義是什麽,因為你想要取得返回值,必然從Optional對象內部去獲得,所以你必然會這麽去思考。但是只是簡單的使用一個Null值會很輕易的讓人忘記去思索代碼所要表達的含義到底是什麽,盡管FindBugs有些幫助,但是我們還是認為它並沒有盡可能的解決好幫助程序員去思索null值代表的含義這個問題。
這種思考會在你返回某些存在的值或者不存在的值的時候顯得特別相關。和其他人一樣,你絕對很可能會忘記別人寫的方法method(a,b)可能會返回一個null值,就好像當你去寫method(a,b)的實現時,你也很可能忘記輸入參數a也可以是null。如果返回的是Optional對象,對於調用者來說,就可以忘卻怎麽去度量null代表的是什麽含義,因為他們始終要從optional對象中去獲得真正的返回值。
Guava學習筆記:Optional優雅的使用null