前言
用kotlin開發android應用,各種新鮮的語法糖層出不窮,真tm香,這篇博文主要記錄一些需要重點注意的地方。
記錄點
1.xxx調用了getXxx()/setXxx方法
toolbar = findViewById(R.id.toolbar)
setSupportActionBar(toolbar)
val actionBar = supportActionBar
在這裏supportActionBar其實時調用了AppCompatActivity
的getSupportActionBar()
方法。
val decorView = window.decorView
decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
window.statusBarColor = Color.TRANSPARENT
在這裏window其實時調用了Activity
的getWindow()
方法。
2.Kotlin null safety相關 ? 和!!和 ?:
先闡述兩個概念:
“?”加在變量名後,系統在任何情況不會報它的空指針異常。
“!!”加在變量名後,如果對象爲null,那麼系統一定會報異常!
先拿Java代碼舉個例子
ArrayList<String> myList = null; // 創建一個null的隊列
Log.d("TAG", "-->> List Size = " + myList.size());
這個例子中,執行到Log打印隊列長度時,大家都知道系統一定會報NullPointerException。然而如果在KT中,在調用myList的時候在它後面加上一個問號myList?.size()
,當myList爲null的時候直接會打印List Size = null
並不會有null異常出現。
當使用Android Studio把上面那段Java自動轉換成KT代碼寫法後:
val myList : ArrayList<String>? = null
Log.d("TAG", "-->> List Size = ${myList!!.size}")
編譯器爲什麼自動把myList.size()
變成了myList!!.size
呢,爲什麼加上的是感嘆號不是問號。
這是因爲編譯器在轉化時爲了保證代碼轉化前後的一致性所造成的。換句話說,在Java上出異常的,轉化到KT上,編譯器任然會讓他保持拋出異常,NullPointerException也是如此。
所以結合上下文可以看得出,!!加上去後好像並沒有和之前Java代碼有什麼區別嘛,該null的地方任然會拋出異常。所以大多數情況下都會使用?來檢測null,輪不到!!出場。!!只會在你需要對某對象進行非空判斷,並且需要拋出異常時纔會使用到。
那我們接下來着重講解一下?到底怎麼用。
在聲明對象時,把它跟在類名後面,表示這個類允許爲null;
在調用對象時,把它跟在對象後面,表示如果爲null程序就會視而不見。
如下列代碼:
// 這是聲明一個變量,問號跟在類名後面
var room: Room? = Room()
private fun checkRoom() {
// 因爲加上了問號,所以可以任意的把room變成空
room = null
// 因爲在調用時加上了問號,所以程序不會拋出異常
Log.d("TAG", "-->> room name = ${room?.roomName}")
}
再舉個不用?的例子:
// 這樣程序就默認的給room加上了!!,從此以後room不允許爲null
var room: Room = Room()
private fun checkRoom() {
// 當把null賦給room時,從編譯的時候就已經不通過
room = null
// 並且編譯器建議把對象後面的問號刪除,因爲這個對象永遠不爲空
Log.d("TAG", "-->> room name = ${room.roomName}")
}
所以加上?是一種安全的寫法,它體現了Kotlin null safety的特性。
KT的語法很靈動,定義參數還可以寫成
val room: Room? = Room() // 先實例化一個room,並且room可以爲空
val room: Room? = null // 不實例化了,開始room就是空的
val room: Room = Room() // 實例化一個room,並且room永遠不能爲空
val room = Room() // 和上一行代碼一樣,是KT最常用的簡寫語法
然而加上問號以後程序就萬事大吉永遠擺脫了NullPointerException的煩惱?我們再看下一段代碼:
val roomList: ArrayList<Room>? = null
if (roomList?.size > 0) {
Log.d("TAG", "-->> 房間數不是0")
}
當我們判斷list.size的時候,編譯器會告訴我們”Operator call corresponds to a dot-qualified call ‘roomList?.size.compareTo(0)’ which is not allowed on a nullable receiver ‘roomList?.size’.”。大概意思是,當roomList爲null的時,它的size返回就是”null”,但是”null”不可以和int值比大小,所以編譯器建議我們寫成roomList?.size!! > 0
。
沒錯,經過編譯器的建議加上了!!,我們程序運行到這行代碼,roomList爲null時它一定會報異常。所以是不是必須得在外面套一層if(roomList != null)
這種Java常見語句才能避免異常嗎?
當然Kotlin不會讓程序出現這種囉嗦的代碼,所以裏面提供了對象A ?: 對象B
表達式,並且取消了Java中的條件表達式 ? 表達式1 : 表達式2
這個三元表達式。
?:表示的意思是,當對象A值爲null的時候,那麼它就會返回後面的對象B。
val roomList: ArrayList<Room>? = null
val mySize= roomList?.size ?: 0
此時mySize的值就爲0,因爲roomList?.size爲空。
所以我們可以把上面的代碼改成這樣:
val roomList: ArrayList<Room>? = null
if (roomList?.size ?: 0 > 0) { // 這一行添加了?:
Log.d("TAG", "-->> 房間數不是0")
}
就目前爲止使,用上面的?和?:基本上能避免程序中出現的所有NullPointerException
3.let,with,run,apply,also函數區別
通過以上幾種函數的介紹,可以很方便優化kotlin中代碼編寫,整體看起來幾個函數的作用很相似,但是各自又存在着不同。使用的場景有相同的地方比如run函數就是let和with的結合體。下面一張表格可以清晰對比出他們的不同之處。
函數名 | 定義inline的結構 | 函數體內使用的對象 | 返回值 | 是否是擴展函數 | 適用的場景 |
---|---|---|---|---|---|
let | fun T.let(block: (T) -> R): R = block(this) | it指代當前對象 | 閉包形式返回 | 是 | 適用於處理不爲null的操作場景 |
with | fun with(receiver: T, block: T.() -> R): R = receiver.block() | this指代當前對象或者省略 | 閉包形式返回 | 否 | 適用於調用同一個類的多個方法時,可以省去類名重複,直接調用類的方法即可,經常用於Android中RecyclerView中onBinderViewHolder中,數據model的屬性映射到UI上 |
run | fun T.run(block: T.() -> R): R = block() | this指代當前對象或者省略 | 閉包形式返回 | 是 | 適用於let,with函數任何場景。 |
apply | fun T.apply(block: T.() -> Unit): T { block(); return this } | this指代當前對象或者省略 | 返回this | 是 |
1、適用於run函數的任何場景,一般用於初始化一個對象實例的時候,操作對象屬性,並最終返回這個對象。 3、一般可用於多個擴展函數鏈式調用 |
also | fun T.also(block: (T) -> Unit): T { block(this); return this } | | it指代當前對象 | 返回this | 是 | 適用於let函數的任何場景,一般可用於多個擴展函數鏈式調用 |
egg:
fun showProgressDialog(title: String?, message: String) {
if (progressDialog == null) {
progressDialog = ProgressDialog(this).apply {
if (title != null) {
setTitle(title)
}
setMessage(message)
setCancelable(false)
}
}
progressDialog?.show()
}
fun closeProgressDialog() {
//表示object不爲null的條件下,纔會去執行let函數體
progressDialog?.let {
if (it.isShowing) {
it.dismiss()
}
}
}