在我們學(xué)習(xí)和使用Guava的Optional之前蠢棱,我們需要來了解一下Java中null。因為覆享,只有我們深入的了解了null的相關(guān)知識靶橱,我們才能更加深入體會領(lǐng)悟到Guava的Optional設(shè)計和使用上的優(yōu)雅和簡單寥袭。
NullPointerException,大家應(yīng)該都見過。這是Tony Hoare在設(shè)計ALGOL W語言時提出的null引用的想法关霸,他的設(shè)計初衷是想通過編譯器的自動檢測機制传黄,確保所有使用引用的地方都是絕對安全的。很多年后队寇,他對自己曾經(jīng)做過的這個決定而后悔不已膘掰,把它稱為“我價值百萬的重大失誤”。它帶來的后果就是---我們想判斷一個對象中的某個字段進行檢查英上,結(jié)果發(fā)現(xiàn)我們查看的不是一個對象炭序,而是一個空指針啤覆,他會立即拋出NullPointerException異常苍日。
Java中null的使用有時候會產(chǎn)生一些意想不到的內(nèi)傷:
1.無法表達具體的業(yè)務(wù)含義,語義含糊不清窗声;
2.增加了NullPointException的發(fā)生相恃,因為不知道什么地方就返回了一個null;
3.null和空容易混為一談笨觅;
4.需要非null判斷拦耐,弱可讀性、代碼不夠優(yōu)雅见剩。返回一個null值絕對不是一個好的選擇杀糯,所以,對于null關(guān)鍵字盡量避免使用苍苞。
空指針是我們最常見也最討厭的異常固翰,為了防止空指針異常狼纬,你不得在代碼里寫大量的非空判斷。
null值是一種令人不滿的模糊含義骂际。有的時候會產(chǎn)生二義性疗琉,這時候我們就很難搞清楚具體的意思,如果程序返回一個null值歉铝,其代表的含義到底是什么盈简,例如:Map.get(key)若返回value值為null,其代表的含義可能是該鍵指向的value值是null太示,亦或者該鍵在map中并不存在柠贤。null值可以表示失敗,可以表示成功类缤,幾乎可以表示任何情況种吸。用其它一些值(而不是null值)可以讓你的代碼表述的含義更清晰。
ConcurrentHashMap不允許為空
The main reason that nulls aren’t allowed in ConcurrentMaps
(ConcurrentHashMaps, ConcurrentSkipListMaps) is that
ambiguities that may be just barely tolerable in non-concurrent
maps can’t be accommodated. The main one is that if
map.get(key) returns null, you can’t detect whether the
key explicitly maps to null vs the key isn’t mapped.
In a non-concurrent map, you can check this via map.contains(key),
but in a concurrent one, the map might have changed between calls.
Guava文檔中呀非,第一篇就提到的盡量避免使用Null坚俗,會給代碼帶來一些負面影響,并舉出map.get(key) == null岸裙,帶來的混淆猖败。由此。Guava提出了Optional的概念降允。
Guava用Optional表示可能為null的T類型引用恩闻。一個Optional實例可能包含非null的引用(我們稱之為引用存在),也可能什么也不包括(稱之為引用缺失)剧董。它從不說包含的是null值幢尚,而是用存在或缺失來表示。但Optional從不會包含null值引用翅楼。
Guava的Optional有兩種實現(xiàn)尉剩,Absent和Present,這就可以理解為毅臊,傳統(tǒng)代碼書寫方式中的null和non-null理茎。而Guava中Absent和Present中重寫Optional的isPresent()方法。
類聲明
@GwtCompatible(serializable = true)
public abstract class Optional<T> implements Serializable
Optional.of(T)
public void test1(){
Integer num = null;
Optional<Integer> op1 = Optional.of(num); // java.lang.NullPointerException
System.out.println(op1.get());
}
上面的程序管嬉,我們使用Optional.of(null)方法皂林,這時候程序會第一時間拋出空指針異常,這可以幫助我們盡早發(fā)現(xiàn)問題蚯撩。如果給定值不為null础倍,則會返回給定值的Optional實例。
public static <T> Optional<T> of(T reference) {
return new Present<T>(checkNotNull(reference));
}
首先使用checkNotNull來判斷給定值是否為null胎挎,如果為null沟启,則會拋出空指針異常扰楼,否則返回給定值的Optional的實例(Present是Optional的子類)。
Optional.absent()
public void test3(){
Integer num = new Integer(4);
Optional<Integer> op = Optional.absent();
Optional<Integer> op2 = Optional.of(num);
System.out.println("op:" + op.isPresent() + " op2:" + op2.isPresent());
}
上面的程序美浦,我們使用Optional.absent()方法弦赖,創(chuàng)建引用缺失的Optional實例。 源碼:
public static <T> Optional<T> absent() {
return Absent.withType();
}
static final Absent<Object> INSTANCE = new Absent<Object>();
@SuppressWarnings("unchecked") // implementation is "fully variant"
static <T> Optional<T> withType() {
return (Optional<T>) INSTANCE;
}
通過withType方法返回一個靜態(tài)Absent對象浦辨,并強制轉(zhuǎn)換為Optional對象蹬竖。從上面就可以看出其中不包含任何的引用。
Optional.fromNullable(T)
創(chuàng)建指定引用的Optional實例流酬,若引用為null則表示缺失币厕,返回應(yīng)用缺失對象Absent,否則返回引用存在對象Present芽腾。
public void test4(){
Integer num1 = null;
Integer num2 = new Integer(4);
Optional<Integer> op1 = Optional.fromNullable(num1); // 引用缺失
Optional<Integer> op2 = Optional.fromNullable(num2); // 引用存在
System.out.println("op1:" + op1.isPresent() + " op2:" + op2.isPresent()); // false true
}
}
public static <T> Optional<T> fromNullable(@Nullable T nullableReference) {
return (nullableReference == null)
? Optional.<T>absent()
: new Present<T>(nullableReference);
}
從上面源碼中可以看出如果T為null旦装,則調(diào)用Optional靜態(tài)方法absent(),表示引用缺失摊滔;如果T不為null阴绢,則創(chuàng)建一個Present對象,表示引用存在艰躺。
T get()
返回Optional包含的T實例呻袭,該T實例必須不為空;否則腺兴,對包含null的Optional實例調(diào)用get()會拋出一個IllegalStateException異常左电。
public void test5(){
Integer num1 = null;
Integer num2 = new Integer(4);
Optional<Integer> op1 = Optional.fromNullable(num1); // 引用缺失
Optional<Integer> op2 = Optional.fromNullable(num2); // 引用存在
System.out.println("op2:" + op2.get()); // 4
System.out.println("op1:" + op1.get()); // java.lang.IllegalStateException: Optional.get() cannot be called on an absent value
}
因為fromNullable對象根據(jù)給定值是否為null,返回不同的對象:
return (nullableReference == null)
? Optional.<T>absent()
: new Present<T>(nullableReference);
因此調(diào)用的get方法也將會不一樣页响。
public abstract T get();
如果返回的是一個Present對象篓足,將調(diào)用Present類中的get()方法:
@Override
public T get() {
return reference;
}
如果返回的是一個Absent對象,將調(diào)用Absent類中的get()方法:
@Override
public T get() {
throw new IllegalStateException("Optional.get() cannot be called on an absent value");
}
T or (T)
返回Optional所包含的引用闰蚕,若引用缺失栈拖,返回指定的值。
public void test6(){
Integer num1 = null;
Integer num2 = new Integer(4);
Optional<Integer> op1 = Optional.fromNullable(num1); // 引用缺失
Optional<Integer> op2 = Optional.fromNullable(num2); // 引用存在
System.out.println("op2:" + op2.or(0)); // 4
System.out.println("op1:" + op1.or(0)); // 0
}
因為fromNullable對象根據(jù)給定值是否為null陪腌,返回不同的對象:
return (nullableReference == null)
? Optional.<T>absent()
: new Present<T>(nullableReference);
因此調(diào)用的or方法也將會不一樣辱魁。
public abstract T or(T defaultValue);
如果返回的是一個Present對象烟瞧,將調(diào)用Present類中的or()方法:
@Override
public T or(T defaultValue) {
checkNotNull(defaultValue, "use Optional.orNull() instead of Optional.or(null)");
return reference;
}
這個方法首先對默認值進行判斷诗鸭,如果不為null,則返回引用参滴;如果為null强岸,拋出空指針異常,這種情況可以使用Optional.orNull()方法代替砾赔。
(2)如果返回的是一個Absent對象蝌箍,將調(diào)用Absent類中的or()方法:
@Override
public T or(T defaultValue) {
return checkNotNull(defaultValue, "use Optional.orNull() instead of Optional.or(null)");
}
這個方法首先對默認值進行判斷青灼,如果不為null,則返回默認值妓盲;如果為null杂拨,拋出空指針異常,這種情況可以使用Optional.orNull()方法代替悯衬。
public void test6(){
String num1 = null;
String num2 = "123";
String defaultNum = null;
Optional<String> op1 = Optional.fromNullable(num1); // 引用缺失
Optional<String> op2 = Optional.fromNullable(num2); // 引用存在
System.out.println("op2:" + op2.or("0")); // 123
System.out.println("op1:" + op1.or(defaultNum)); // java.lang.NullPointerException: use Optional.orNull() instead of Optional.or(null)
}
T orNull()
返回Optional所包含的引用弹沽,若引用缺失,返回null
public void test6(){
String num1 = null;
String num2 = "123";
Optional<String> op1 = Optional.fromNullable(num1); // 引用缺失
Optional<String> op2 = Optional.fromNullable(num2); // 引用存在
System.out.println("op2:" + op2.orNull()); // 123
System.out.println("op1:" + op1.orNull()); // null
}
因為fromNullable對象根據(jù)給定值是否為null筋粗,返回不同的對象:
return (nullableReference == null)
? Optional.<T>absent()
: new Present<T>(nullableReference);
因此調(diào)用的orNull方法也將會不一樣策橘。
@Nullable
public abstract T orNull();
(1)如果返回的是一個Present對象,將調(diào)用Present類中的orNull()方法:
@Override
public T orNull() {
return reference;
}
引用存在娜亿,返回引用丽已。
(2)如果返回的是一個Absent對象,將調(diào)用Absent類中的orNull()方法:
@Override
@Nullable
public T orNull() {
return null;
}
引用缺失买决,返回null沛婴,此時沒有默認值。
注意事項
不要在Set中使用null督赤,或者把null作為map的鍵值瘸味。使用特殊值代表null會讓查找操作的語義更清晰。
如果你想把null作為map中某條目的值够挂,更好的辦法是 不把這一條目放到map中旁仿,而是單獨維護一個”值為null的鍵集合” (null keys)。Map 中對應(yīng)某個鍵的值是null孽糖,和map中沒有對應(yīng)某個鍵的值枯冈,是非常容易混淆的兩種情況。因此办悟,最好把值為null的鍵分離開尘奏,并且仔細想想,null值的鍵在你的項目中到底表達了什么語義病蛉。
使用Optional的意義在哪兒炫加?
- Optional 迫使你積極思考引用缺失的情況 因為你必須顯式地從Optional獲取引用。
- 如同輸入?yún)?shù)铺然,方法的返回值也可能是null俗孝。和其他人一樣,你絕對很可能會忘記別人寫的方法method(a,b)會返回一個null魄健,就好像當你實現(xiàn)method(a,b)時赋铝,也很可能忘記輸入?yún)?shù)a可以為null。將方法的返回類型指定為Optional沽瘦,方法的參數(shù)設(shè)置為Optional革骨,也可以迫使調(diào)用者思考返回的引用缺失的情形农尖。
public static Optional<Integer> sum(Optional<Integer> a,Optional<Integer> b){
if(a.isPresent() && b.isPresent()){
return Optional.of(a.get()+b.get());
}
return Optional.absent();
}
java8 optional實戰(zhàn)
以前寫法
public String getCity(User user) throws Exception{
if(user!=null){
if(user.getAddress()!=null){
Address address = user.getAddress();
if(address.getCity()!=null){
return address.getCity();
}
}
}
throw new Excpetion("取值錯誤");
}
JAVA8寫法 (java8Optional就是受guava Opitonal啟發(fā))
public String getCity(User user) throws Exception{
return Optional.ofNullable(user)
.map(u-> u.getAddress())
.map(a->a.getCity())
.orElseThrow(()->new Exception("取指錯誤"));
}
以前寫法
if(user!=null){
dosomething(user);
}
JAVA8寫法
Optional.ofNullable(user)
.ifPresent(u->{
dosomething(u);
});
以前寫法
public User getUser(User user) throws Exception{
if(user!=null){
String name = user.getName();
if("zhangsan".equals(name)){
return user;
}
}else{
user = new User();
user.setName("zhangsan");
return user;
}
}
現(xiàn)在寫法
public User getUser(User user) {
return Optional.ofNullable(user)
.filter(u->"zhangsan".equals(u.getName()))
.orElseGet(()-> {
User user1 = new User();
user1.setName("zhangsan");
return user1;
});
}