在大多數(shù)程序語(yǔ)言中,我們都需要與Null打交道缚忧,并且糾纏于對(duì)它的檢查中悟泵。一不小心讓它給溜出來(lái),就可能像打開(kāi)潘多拉的盒子一般闪水,給程序世界帶來(lái)災(zāi)難糕非。說(shuō)起來(lái),在我們?nèi)祟愂澜缰卸氐冢琋ull到底算什么“東西”呢峰弹?語(yǔ)義上講,它就是一場(chǎng)空芜果,即所謂“虛無(wú)”鞠呈。這個(gè)世界并沒(méi)有任何物質(zhì)可以代表“虛無(wú)”,因而它僅存于我們的精神層面右钾。說(shuō)虛無(wú)存在其實(shí)是一種悖論蚁吝,因?yàn)榇嬖谄鋵?shí)是虛無(wú)的反面。若從程序本質(zhì)上講舀射,Null代表一種狀態(tài)窘茁,指一個(gè)對(duì)象(或變量),雖獲聲明卻未真正誕生脆烟,甚至可能永遠(yuǎn)不會(huì)誕生山林。而一旦誕生,Null就被抹去了邢羔,回歸了正確的狀態(tài)驼抹。
站在OO的角度來(lái)講,既然Everything is object拜鹤,自然可以將Null同樣視為Object——這近似于前面提到的悖論框冀,既然是Null,為何又是Object呢敏簿?換言之明也,在對(duì)象世界里宣虾,其實(shí)沒(méi)有什么不存在,所謂“不存在”仍然是一種“存在”温数。這么說(shuō)容易讓人變糊涂绣硝,就好像我們搞不清楚“我是誰(shuí)”。所以帆吻,我寧肯采用Martin Fowler的說(shuō)法域那,將Null Object視為一種Special Case咙边,即Null其實(shí)是一種特例猜煮。
視Null為一種特例,即可用OO的特化來(lái)表達(dá)败许。當(dāng)某個(gè)對(duì)象可能存在Null這種狀態(tài)時(shí)王带,都可以將這種狀態(tài)表示為一種特化的類,它不再代表Null市殷,而是代表“什么都不做”愕撰。凡是返回Null的地方,都替換為這個(gè)Null Object醋寝,用以表達(dá)這種Null其實(shí)僅僅是一種特列搞挣。于是乎,我們像抹殺異教徒一般抹去了“虛無(wú)”的存在音羞。(當(dāng)虛無(wú)被抹去囱桨,是什么樣的存在?)
然而嗅绰,若在程序語(yǔ)言中實(shí)現(xiàn)自己的Null Object舍肠,固然可以在一定程度上消除對(duì)Null的檢查,卻存在一些約束:
- 對(duì)于String之類的類型窘面,無(wú)法定義NullString子類翠语;
- 每次都需要自己去定義子類來(lái)表示Null;
- 必須約束團(tuán)隊(duì)不能返回Null财边;
Google的Guava框架為了解決這一問(wèn)題肌括,引入了Optional<T>:
public abstract class Optional<T> implements Serializable {
public static <T> Optional<T> absent() {
return (Optional<T>) Absent.INSTANCE;
}
public static <T> Optional<T> of(T reference) {
return new Present<T>(checkNotNull(reference));
}
public static <T> Optional<T> fromNullable(@Nullable T nullableReference) {
return (nullableReference == null)
? Optional.<T>absent()
: new Present<T>(nullableReference);
}
public abstract boolean isPresent();
public abstract T get();
public abstract T or(T defaultValue);
public abstract <V> Optional<V> transform(Function<? super T, V> function);
}
于是,我們可以這樣來(lái)使用Optional<T>:
public final Optional<E> first() {
Iterator<E> iterator = iterable.iterator();
return iterator.hasNext()
? Optional.of(iterator.next())
: Optional.<E>absent();
}
first()方法返回的是一個(gè)Optional<E>類型酣难。這是Guava中操作集合的一個(gè)方法谍夭。當(dāng)我們要獲得第一個(gè)元素時(shí),可以調(diào)用該方法:
List<Person> persons = newArrayList();
String name = from(persons).first().transform(new Function<Person, String>() {
@Override
public String apply(Person input) {
return input.getName();
}
}).or("not found");
assertThat(name, is("not found"));
不知是巧合鲸鹦,還是一種借鑒慧库,Java 8同樣定義了Optional用以處理這種情況。前面的代碼在Java 8下可以改寫為:
List<Person> persons = newArrayList();
String name = persons.stream().findFirst().map(p -> p.getName()).orElse("not found");
assertThat(name, is("not found"));
其實(shí)在Scala的早期版本馋嗜,就已經(jīng)提供了Option[T]類型齐板。前面的代碼若用scala編寫,就變成:
case class Person(name: String, age: Int)
val persons = List[Person]()
persons.headOption.map(p => p.name).getOrElse("not found")
這樣的設(shè)計(jì)方式,還是Null Object模式嗎甘磨?讓我們回到Null的本原狀態(tài)橡羞,思考為什么會(huì)產(chǎn)生Null?首先济舆,Null代表一種異常狀態(tài)卿泽,即在某種未可知的情形下,可能返回Null滋觉;正常情況下签夭,返回的則是非Null的對(duì)象。Null與非Null椎侠,代表一種未知與不確定性第租。哈姆雷特糾結(jié)于“To be, or not to be, this is a question”,但在程序世界里我纪,可以抽象為一個(gè)集合來(lái)表達(dá)這種非此即彼的狀況慎宾。
從函數(shù)式編程的角度來(lái)講,我們可以將這樣的集合設(shè)計(jì)為一個(gè)Monad浅悉。根據(jù)DSL in Action一書(shū)中對(duì)Monad的介紹趟据,一個(gè)Monad由以下三部分定義:
- 一個(gè)抽象M[A],其中M是類型構(gòu)造函數(shù)。在Scala語(yǔ)言中可以寫成class M[A]术健,或者case class M[A]汹碱,有或者trait M[A]
- 一個(gè)unit方法(unit v)。對(duì)應(yīng)Scala中的函數(shù)new M(v)或者M(jìn)(v)的調(diào)用苛坚。
- 一個(gè)bind方法比被,起到將運(yùn)算排成序列的作用。在Scala中通過(guò)flatMap組合子來(lái)實(shí)現(xiàn)泼舱。bind f m對(duì)應(yīng)的Scala語(yǔ)句是m flatMap f等缀。
同時(shí),Monad還必須滿足以下三條規(guī)則:
- 右單位元(identity)娇昙。即對(duì)于任意Monad m尺迂,有m flatMap unit => m。對(duì)于Option冒掌,unit就是Option伴生對(duì)象定義的apply()方法噪裕。若m為Some("Scala"),則m flatMap {x => Option(x)}股毫,其結(jié)果還是m膳音。
- 左單位元(unit)。即對(duì)于任意Monad m铃诬,有unit(v) flatMap f => f(v)祭陷。
- 結(jié)合律苍凛。即對(duì)于任意Monad m,有m flatMap g flatMap h => m flatMap {x => g(x) flatMap h}兵志。
假設(shè)我們定義一個(gè)函數(shù)f:
def f(v: String) = Option(v)
則Option("Scala") flatMap {x => f(x)}
的結(jié)果就等于f("scala")
醇蝴。
無(wú)論是Scala中的Option[A],還是Java 8中的Optional[T]想罕,都是一個(gè)Monad悠栓。此時(shí)的Null不再是特例,而是抽象Option[A]對(duì)稱的兩個(gè)元素中的其中一個(gè)按价,在Scala中惭适,即Option[T]中的Some[T]或None。它們倆面貌相同俘枫,卻是一對(duì)性格迥異的雙生子腥沽。
在設(shè)計(jì)為Monad后,就可以利用Monad提供的bind功能鸠蚪,完成多個(gè)函數(shù)的組合。組合時(shí)师溅,并不需要考慮返回為None的情況茅信。Monad能保證在前一個(gè)函數(shù)返回空值時(shí),后續(xù)函數(shù)不會(huì)被調(diào)用墓臭。讓我們來(lái)看一個(gè)案例蘸鲸。例如,我們需要根據(jù)某個(gè)key從會(huì)話中獲得對(duì)應(yīng)的值窿锉,然后再將該值作為參數(shù)去查詢符合條件的特定Customer酌摇。在Scala中,可以將這兩個(gè)步驟定義為函數(shù)嗡载,返回結(jié)果分別為Option[String]與Option[Customer]:
def params(key: String): Option[String]
def queryCustomer(refId: String): Option[Customer]
val customer =
(
for {
r <- params("customerId")
c <- queryCustomer(r)
} yield c
) getOrElse error("Not Found")
這段代碼用到了Scala的for comprehension窑多,它實(shí)則是對(duì)flatMap和map的一種包裝。尤其當(dāng)嵌套多個(gè)flatMap和map時(shí)洼滚,使用for comprehension會(huì)更加直觀可讀埂息。翻譯為flatMap,則為:
params("customerId").flatMap{
r => queryCustomer(r).map {
c => c
}
} getOrElse error("Not Found")
當(dāng)我最初看到Guava設(shè)計(jì)的Optional[T]時(shí)遥巴,我以為是Null Object模式的體現(xiàn)千康。顯然,它的功能要超出Null Object的范疇铲掐。但它也并非Monad拾弃,在前面給出的定義中,我們可以看到Guava的Optional[T]僅提供了map(即定義中的transform)功能摆霉,而沒(méi)有提供更基本的flatMap操作豪椿。具有函數(shù)式編程功能的Scala與Java 8加強(qiáng)了這一功能颠毙,利用Monad強(qiáng)化了程序?qū)ull的處理。