本文由 KnewHow 發表在 ScalaCool 團隊博客。編程
最近閱讀一些關於 Kotlin 類型系統方面的書,發現 Kotlin 的類型系統針對 null
有着獨特的設計哲學。在 Java 或者其它編程語言中,常常會出現 NullPointerException
,而致使此異常的重要緣由是由於你能夠寫 String s = null
這樣的代碼。其實能夠認爲這是 Java 等語言類型系統設計的一個缺陷,它們容許 null
能夠做爲任何類型的值!安全
可是在 Kotlin 中,若是你聲明 val s: String = null
,那麼編譯器會給你一個 error,由於在 Kotlin 中,你不容許把一個 null
值賦給一個普通的類型。若是你聲明一個這樣的函數 fun strLen(s: String) = {...}
,那麼這個函數將不接受值爲 null
的參數。編程語言
這個設計看起來如此的美好,他能夠極大程度的減小 Kotlin 產生 NullPointerException
,但是若是有一天,你須要調用一個方法,它的返回值可能爲 null
也可能爲 String
,那麼在 Kotlin 中你能夠聲明一個可空的字符串類型:String?
。val s: String? = null
此時 Kotlin 的編譯器會讓這行代碼經過。固然它也能夠接收一個普通的 String
類型的值 val s: String? = "abc"
。函數
可空類型(Type?
)的設計,是 Kotlin 另外一個設計哲學,它要求工程師在設計的時候就須要肯定該變量是否可爲空。若是不爲空就使用Type
類型聲明,不然就使用 Type?
類型聲明。這讓我想起在 Scala 中存在一個和 Type?
有着殊途同歸之妙的一個類型—— Option[T]
。性能
Option[T]
有兩個子類型:Some[T]
和 None
,你可使用 val s: Option[String] = Some("123")
來表示一個字符串存在,固然你可使用val s: Option[String] = None
來表示這個字符串不存在。測試
Scala 和 Kotlin 都是基於 JVM 的編程語言,而 Option[T]
和 Type?
的設計就是用來解決 JVM 平臺出現的 NullPointerException
。但兩者的設計理念卻大相徑庭,Scala 的 Option[T]
是在原有類型基礎上使用 Option
作一層封裝,而 Koltin 的 Type?
是使用語法糖完成的。ui
那麼這兩種設計方案到底誰更好一點呢?咱們將會使用如下標準來分別測試它們:spa
NullPointerException
—— 兩者的設計都是爲了解決 NullPointerException
,誰能夠更好的規避這個問題呢?在上文中,咱們曾經提過,NullPointerException
產生的緣由是你能夠把一個 null
的值傳遞給一個類型的變量,而後調用這個類型的方法。咱們可使用 Java 的代碼來表示一下:String s = null; s.length()
。scala
在 Type?
的設計理念中,對於不肯定是否爲 null
類型可使用 Type?
類型來聲明,如val s: String? = getString...
,此時 s
的類型是 String?
,你不能直接調用 s.length
,你須要進行安全調用s?.length
。這個函數的返回類型是一個 Int?
,這很正常,對於一個不肯定是否爲 null
的類型進行安全調用返回固然是一個 Type?
類型。若是 s
不爲 null
正常返回 s
的長度,不然返回 null
。除此以外, Kotlin 還針對 Type?
提供了 Elvis 操做和 let 函數,具體的用法能夠參考 Kotlin 官方手冊。設計
而在 Optional
的設計哲學中,你可使用 Option[T]
來包裹一個不肯定是否爲 null
的值。這裏咱們使用 Scala 的代碼來演示:val s: Option[String] = Option(getString...)
,此時 s
的類型爲 Option[String]
,你仍然不能直接調用s.length
,你可使用 map
函數:s.map(s => s.length)
,它的返回值是一個 Option[Int]
類型。和 Type?
很相似,對一個 Option[T]
類型使用 map
函數,結果固然是一個 Option[S]
類型。在 Scala 中,你也可使用模式匹配來處理 Option
類型。
總結:兩者均可以完美的規避 NullPointerException
,Type?
使用安全調用來避免直接調用 Type
類型的方法,而 Option
則使用 map 函數或者模式匹配來處理。本質上都是避免直接調用值可能爲 null
的類型變量的方法。
實際的業務是比較複雜的,例如,咱們須要計算兩個數字字符串的乘積,首先咱們須要把他們轉換爲 Int
類型,若是其中一個字符串是轉換失敗,則沒法計算結果。
在 Kotlin 的 Type?
中,咱們須要從新定義 String
類型的 toInt
方法,讓它返回一個 Int?
類型,代碼以下:
fun tryString2Int(a: String) = try {
a.toInt()
}catch (e:Exception){
null
}
複製代碼
而後咱們須要定義一個方法來計算兩個數字字符串的乘積,這裏咱們使用 Type?
的 let 函數,它接受一個 Lambda 表達式,若是調用者的值不爲 null
,則調用 Lambda 表達式,不然直接返回 null
。strNumberMuti
函數返回的是一個 Double?
類型,若是有任何一個字符串轉換數字失敗,就返回 null
,都轉換成功才計算乘積。
fun strNumberMuti(s1: String, s2: String): Double? =
tryString2Int(s1)?.let{ a ->
tryString2Int(s2)?.let {
t -> a * t * 1.0 }}
複製代碼
這段代碼的可讀取有點差呀,並且在實際的業務開發過程當中,可能會有更多的 Type?
類型,那代碼豈不是要爆炸了!。幸運的是,Kotlin 容許咱們使用 if
來代替 let
函數 作相同的判斷,代碼以下:
fun strNumberMuti2(s1: String, s2: String):Double? {
val a = tryString2Int(s1)
val b = tryString2Int(s2)
return if(a!=null && b!= null) a * b * 1.0 else null
}
複製代碼
這樣的代碼可讀性就好多了,可是丟失函數式的編程美感。並且感受 Type?
是一種語法糖,手動對 Type?
進行非空校驗,就能夠直接使用 Type
類型了!!
一樣的咱們使用 Scala 的 Option[T]
來完成上面的需求,爲了讓 toInt
函數返回 Option[T]
類型,咱們定義了一個 Try
函數,這個函數看不懂不要緊,你只需知道它接受一個函數,而且返回一個 Option[A]
值便可。代碼讓以下:
def Try[A](a: => A): Option[A] = {
try Some(a)
catch {case e: Exception => None}
}
複製代碼
一樣的,咱們須要寫一個函數,用來把兩個字符串數字轉換爲整數,而且作它們的乘積,這裏咱們爲了使代碼更簡潔,使用了 Scala 的 for 推導,具體的用法能夠參考 Scala 官方的 Document。strNumberNu
返回類型是 Option[Double]
,若是有任何一個轉換失敗,返回 None
,不然返回 Some[Double]
,代碼以下:
def strNumberMuti(s1: String, s2: String): Option[Double] = {
for{
a <- Try{ s1.toInt }
b <- Try{ s2.toInt }
} yield a * b
}
複製代碼
能夠看出,使用 Scala 的 Option[T]
更具備函數式的編程美感,並且代碼的可讀性極強,並且即便有更多的 Option[T]
,for 推導均可以輕鬆應對。
總結:面對比較複雜的業務場景,Type?
和 Option[T]
均可以輕鬆應對,可是 Type?
的用法就顯得有些 low,仍是使用 !=null
的套路,這也暴露了它的設計是存在缺陷的。相反的 Option[T]
的設計理念是完備的,並且極具函數式的編程美感。
性能是衡量設計好壞的一個重要的方面,下面咱們只作一個簡單的測試:讓兩個字符串都是"999"
,而後分別執行 Kotlin 的 strNumberMuti
和 Scala 的 strNumberMuti
一千萬次,而後咱們發現 Kotlin 的 strNumberMuti
執行時間大約在 1.9s,而 Scala 的 strNumberMuti
執行時間約在 5.0s。由此能夠看出,Kotlin 的 Type?
比 Scala Option[T]
擁有更好的性能,其實這樣很正常,由於 Kotlin 的 Type?
是語法糖,建立一個 Type?
的對象其實和建立一個 Type
的對象其實消耗的性能差很少,可是 Option[T]
不只僅須要建立 T
類型的對象,更須要建立 Option[T]
類型的對象來包裹 T
類型的對象,所以它的開銷大一點。
就我而言,我更喜歡 Scala 的 Option[T]
的設計,由於它是理論完備的,並且極具函數式的編程美感,即便它的性能要差一點。對於 Kotlin 的 Type?
類型,我以爲它的設計有瑕疵,就拿 let
函數舉例,在單個 Type?
很好用,可是當多個 Type?
進行組合的時候,就顯得很雞肋。
蘿蔔青菜,各有所愛,也許某天 Kotlin 也會讓 Type?
具備函數式的編程美感。