kotlin對可空類型的顯式支持,是kotlin類型系統中幫助你避免NullPointerException錯誤的特性bash
若是一個變量能夠爲null,對其方法的調用即是不安全的,會致使NullPointerException 。先看java中的函數:ide
/* java */
int strLen(String s ){
return s.length();
}
複製代碼
這個函數並非安全的,當傳的實參是null,便會拋出NullPointerException
(固然能夠先提早檢查)函數
現假設你不但願傳入實參爲null,則以下使用非空類型,即不容許爲null:工具
fun strLen(s:String) = s.length
複製代碼
當傳入null實參時,編譯器會報錯:post
>>> strLen(null)
ERROR:Null can not be a value of a non-null type string
複製代碼
上述函數,參數被聲明爲String類型,即非空的,因此不能傳遞一個null值。若想使該參數可空,便可傳入null值,需顯式在類型名稱後面加上問號來標記:優化
fun strLenSafe(s:String?) = if(s == null) 0 else s.length
複製代碼
能夠在任意類型後面添加問號來表示能夠存儲null引用,如String?,Int?,MyCustomType? 即,Type? = Type or nullui
注: 除非顯式的把它標記爲可空,不然因此常見類型默認時可空的this
對於可空類型,不能直接調用它的方法或屬性,也不能把它賦值給非空類型的變量,這都是不安全的:
>>> fun strLenSafe(s:String?) = s.length()
ERROR: only safe(?.) or non-null asserted(!!.) calls are allowed ...
>>> val x:String? = null
>>> val y:String = x
ERROR: Type mismatch...
複製代碼
可空值最重要操做就是與null作比較,而一旦檢查後,編譯器便會記住並提供只能轉換,如:
fun strLenSafe(s:String?) = if(s == null) 0 else s.length // 智能轉換爲非空
複製代碼
一味用if檢查,過於冗餘,kotlin提供了一些運算符工具,能幫助咱們用簡潔的方式來處理可空值。
安全調用運算符"?.",將一次null檢查和一個方法調用(或屬性訪問)合併成一個操做。表達式s?.toUpperCase()
等同於這種繁瑣寫法if (s!=null)s.toUpperCase()else null
即若是調用一個非空值的方法,此次方法調用會被正常執行,但若是是null,則調用不會發生,整個表達式值爲null。
注: 這種表達式的結果類型也是可空類型
fun printlnAllCaps(s:String?){
val allCaps:String? = s?.toUpperCase()
println(allCaps)
}
>>>printlnAllCaps("abc")
ABC
>>>printlnAllCaps(null)
null
複製代碼
若是對象調用鏈中,有多個可空類型的屬性,一般能夠在同一個表達式中方便地連接多個安全調用,以下訪問公司地址:
class Address(val streetAdress:String,val zipCode:Int,val city:String,val country:String)
class Company(val name :String,val address:Address)
class Person(val name:String,val company:Company?)
fun Person.countryName():String {
val country:String? = this.company?.address?.country
return if(country == null) "Unknown" else country //此處可優化
}
>>> val person = Person("John",null)
>>> println(person.countryName())
Unknown
複製代碼
上面例子if(country == null) "Unknown" else country
,用一個值和null比較,若是不爲空就返回這個值,不然返回其餘值。kotlin能對這種狀況進行優化去掉重複代碼:
fun foo(s:String?){
val t:String = s ?: "" //若是s爲空,則結果是空字符串,不然則是s自己
}
複製代碼
Elvis運算符接收兩個運算數,若是第一個運算數不爲null,運算結果就是第一個運算數;不然結果就是第二個運算數,即 foo ?: bar
的繁瑣表示爲:
if(foo ==null) bar else foo
複製代碼
能夠簡化上面打印countryName的例子:
fun Person.countryName() =
company?.address?.country ?: "Unknown"
複製代碼
瞭解了"if非空"檢查後,接下來介紹kotlin經常使用的instanceof檢查的安全版本:經常與安全調用及Elvis運算符一塊兒出現的安全轉換運算符
類型轉換的常規kotlin運算符:as運算符,與java類型轉換同樣,若被轉換的值不是你試圖轉換的類型,就會拋出ClassCastException異常。固然能夠結合is來檢查,但kotlin有更安全優雅的調用方式:as?
as? 運算符嘗試把值轉換成指定類型,若是不是合適的類型,則最終值爲null 。 即foo as? Type
的繁瑣表達爲:
if(foo is Type) foo as Type else null
複製代碼
一種經常使用方式是把安全轉換和Elvis運算符結合使用。 如equals方法的實現很是方便
class Person(val firstName:String,val lastName:String){
override fun equals(o:Any?):Boolean{
val otherPerson = o as? Person ?: return false
return otherPerson.firstName == firstName &&
otherPerson.lastName == lastName
}
override fun hashCode():Int =
firstName.hashCode()*37 + lastName.hashCode()
}
>>> p1 = Person("John","Cap")
>>> p2 = Person("John","Cap")
>>> println(p1 == p2)
true
>>> println(p1.equals(42))
false
複製代碼
安全調用、安全轉換和Elvis運算符都很是高效有用。但有時不須要處理null值,只須要直接告訴編譯器這並非個null值:
非空斷言是kotlin提供的,最簡單粗暴直率的處理。能夠把任何類型轉換爲非空類型。 若是對null值作了非空斷言,則會拋出空異常。即"foo!!"的繁瑣表達爲:
foo as?Foo ?: throw NullPointerException(...)
複製代碼
可使用這種斷言來把可空參數轉換爲非空:
fun ignoreNull(s:String?){
val sNotNull :String = s!! //轉換爲非空
println(sNotNull)
}
複製代碼
本質上是在代表:「我明確知道這個值不爲null,若是我錯了我準備好接收這個異常」
let函數讓處理可空表達式變得容易。let函數所作的全部事情,就是把一個調用它的對象變成lambda表達式的參數。
val s :String? ...
s.let{
//此時lambda參數爲 String?
}
s?.let{
//此時lambda參數爲 String
}
複製代碼
若是和安全運算符使用,就能有效地把調用let函數的可空對象轉爲非可空對象。 即 foo?.let{...}
的繁瑣表達是當foo不爲null時,調用lambda中的代碼,當foo爲null時,則什麼也不發生
如傳遞一個可空參數到一個接收非空參數的函數:
fun sendEmailTo(email:String){...}
>>>val email:String? = ...
>>> sendEmailTo(email)
ERROR:Type mismatch...
複製代碼
但能夠這麼優雅安全地調用:
email?.let{sendEmailTo(it)}
複製代碼