kotlin延遲初始化和密封類詳細(xì)講解
對(duì)變量延遲初始化
Kotlin語(yǔ)言有許多特性,包括變量不可變,變量不可為空,等等。這些特性都是為了盡可能地保證程序安全而設(shè)計(jì)的,但是有些時(shí)候這些特性也會(huì)在編碼時(shí)給我們帶來(lái)不少麻煩。
比如,你的類中存在許多全局變量實(shí)例,為了保證他們能夠滿足kotlin的空指針檢查語(yǔ)法標(biāo)準(zhǔn),你不得不做出許多的非空判斷保護(hù)才行,即使你非常確定它們不會(huì)為空。
通過(guò)一個(gè)例子:
class MainActivity:AppCompatActivity(),View.OnClickListener{
private var adapter:MsgAdapter?=null
override fun onCreate(savedInstanceState:Bundle?){
...
adapter=MsgAdapter(msgList)
...
}
override fun onClick(v:View?){
...
adapter?.notifyItemInserted(msgList.size-1)
...
}
}
這里我們將adapter設(shè)置了全局變量,但是它的初始化工作是在onCreate()方法中進(jìn)行的,因此不得不先將adapter賦值為null,同時(shí)把它的類型聲明成MsgAdapter?。
雖然我們會(huì)在onCreate()方法中對(duì)adapter進(jìn)行初始化,同時(shí)能確保onClick()方法必然在onCreate()方法之后才會(huì)調(diào)用,但是我們?cè)趏nClick()方法中調(diào)用adapter的任何方法時(shí)仍然要進(jìn)行判空處理才行,否則編譯肯定無(wú)法通過(guò)。
而當(dāng)你的代碼中有了越來(lái)越多的全局變量實(shí)例時(shí),這個(gè)問(wèn)題就會(huì)變得越來(lái)越明顯,到時(shí)候你可能必須編寫(xiě)大量額外的判空處理代碼,只是為了滿足kotlin編譯器的要求。
這個(gè)問(wèn)題其實(shí)是可以解決的,而且非常簡(jiǎn)單,那就是對(duì)全局變量進(jìn)行延遲初始化。
延遲初始化使用的是lateinit關(guān)鍵字,它可以告訴kotlin編譯器,我會(huì)在晚些時(shí)候?qū)@個(gè)變量進(jìn)行初始化,這樣我就不用在一開(kāi)始的時(shí)候就將它賦值為null了。
接下來(lái)我就就使用延遲初始化的方式對(duì)上述代碼進(jìn)行優(yōu)化,如下所示:
class MainActivity:AppCompatActivity(),View.OnClickListener{
private lateinit var adapter:MsgAdapter
override fun onCreate(savedInstanceState:Bundle?){
...
adapter=MsgAdapter(msgList)
...
}
override fun onClick(v:View?){
...
adapter.notifyItemInserted(msgList.size-1)
...
}
}
可以看到,我們?cè)赼dapter變量的前面加上lateinit關(guān)鍵字,這樣就不用在一開(kāi)始的時(shí)候?qū)⑺x值為null,同時(shí)類型聲明也就可以改成MsgAdapter了,由于MsgAdapter是不可為空的類型,所以我們?cè)趏nClick()方法中也就不再需要進(jìn)行判空處理,直接調(diào)用adaper的任何方法就可以了。
當(dāng)然,使用lateinit關(guān)鍵字也不是沒(méi)有任何風(fēng)險(xiǎn),如果在adapter變量還沒(méi)有初始化的情況下就直接使用它,那么程序就一定會(huì)奔潰,并且拋出一個(gè)UninitializedPropertyAccessException異常。
所以,當(dāng)你對(duì)一個(gè)全局變量使用了lateinit關(guān)鍵字時(shí),請(qǐng)一定要確保它在被任何地方調(diào)用之前已經(jīng)完成了初始化工作,否則Kotlin將無(wú)法保證程序的安全性。
另外,我們還可以通過(guò)代碼來(lái)判斷一個(gè)全局變量是否已經(jīng)完成了初始化,這些某些時(shí)候能夠有效地避免對(duì)某一個(gè)變量進(jìn)行初始化操作,示例代碼如下:
class MainActivity:AppCompatActivity(),View.onClickListener{
private lateinit var adapter:MsgAdapter
override fun onCreate(savedInstanceState:Bundle?){
...
if(!::adapter.isInitialized){
adapter=MsgAdapter(msgList)
}
...
}
}
::adapter.isInitialized可用于判斷adapter變量是否已經(jīng)初始化。然后我們?cè)賹?duì)結(jié)果進(jìn)行取反,如果還沒(méi)有初始化,那就立即對(duì)adapter變量進(jìn)行初始化,否則什么都不做。
使用密封類優(yōu)化代碼
密封類通??梢越Y(jié)合RecyclerView適配器中的ViewHolder一起使用,它可以在很多時(shí)候幫助你寫(xiě)出更加規(guī)范和安全的代碼。
通過(guò)一個(gè)例子:
新建一個(gè)Kotlin文件
interface Result{
}
class Success(val msg:String):Result
class Failure(val error:Exception):Result
這里定義一個(gè)Result接口,用于表示某個(gè)操作的執(zhí)行結(jié)果,接口中不用編寫(xiě)任何內(nèi)容,然后定義了兩個(gè)類去實(shí)現(xiàn)Result接口:一個(gè)Success類用于表示成功時(shí)的結(jié)果,一個(gè)Failure類用于表示失敗時(shí)的結(jié)果。
接下來(lái)定義一個(gè)getResultMsg()方法,用于獲取最終執(zhí)行結(jié)果的信息,代碼如下:
fun getResultMsg(result: Result)=when(result){
is Success-> result.msg
is Failure-> result.error
else -> throw IllegalArgumentException()
}
getResultMsg()方法中接收一個(gè)Result參數(shù),我們通過(guò)when語(yǔ)句來(lái)判斷:如果Result屬于Success,那么就返回成功的消息;如果Result屬于Failure,那么就返回錯(cuò)誤信息。接下來(lái)我們不得不再編寫(xiě)一個(gè)else條件,否則Kotlin編譯器會(huì)認(rèn)為這里缺少條件分支,代碼將無(wú)法編譯通過(guò)。但實(shí)際上Result的執(zhí)行結(jié)果只可能是Success或Failure,這個(gè)else條件永遠(yuǎn)走不到,所以我們?cè)谶@里直接拋出異常,只是為了滿足kotlin編譯器的語(yǔ)法檢查而已。
但是else還有一個(gè)潛在風(fēng)險(xiǎn),如果我們現(xiàn)在新增一個(gè)Unkown類并實(shí)現(xiàn)Result接口,用于表示未知的執(zhí)行結(jié)果,但是如果沒(méi)有在getResultMsg()方法中添加相應(yīng)的條件分支,編譯器這種情況下不會(huì)提醒我們而是直接運(yùn)行進(jìn)入else條件里面。
這個(gè)時(shí)候密封類可以解決這個(gè)問(wèn)題,密封類的關(guān)鍵字是sealed class,將Result接口改造成密封類的寫(xiě)法:
sealed class Result{
}
class Success(val msg:String): Result()
class Failure(val error:Exception):Result()這個(gè)時(shí)候會(huì)發(fā)現(xiàn)getResultMsg()方法中的else條件已經(jīng)不需要了,如下所示
fun getResultMsg(result: Result)=when(result){
is Success-> result.msg
is Failure-> result.error
}
這是因?yàn)楫?dāng)在when語(yǔ)句中傳入一個(gè)密封類變量作為條件時(shí),Kotlin編譯器會(huì)自動(dòng)檢查該密封類有哪些子類,并強(qiáng)制要求你每一個(gè)子類所對(duì)應(yīng)的條件全部處理。這樣就可以保證,即使沒(méi)有編寫(xiě)else條件,也不可能會(huì)出現(xiàn)漏寫(xiě)條件分支的情況。而如果我們新增一個(gè)Unknown類,并也讓它繼承自Result,此時(shí)getResultMsg()方法就一定會(huì)報(bào)錯(cuò),必須新增一個(gè)Unknown的條件分支才能讓代碼編譯通過(guò)。
密封類及其所有子類只能定義在同一個(gè)文件的頂層位置,不能嵌套在其他類中,這是被密封類底層的實(shí)現(xiàn)機(jī)制所限制的。
接下來(lái)看一下它是如何結(jié)合MsgAdapter中的ViewHolder一起使用,并優(yōu)化一下MsgAdapter中的代碼。
比如在MsgAdapter中的onBindViewHolder()方法中存在一個(gè)沒(méi)有實(shí)際作用的else條件,只是拋出一個(gè)異常而已。對(duì)于這部分的代碼,我們就可以借助密封類的特性來(lái)進(jìn)行優(yōu)化。新建一個(gè)MsgViewHolder.kt文件,其中加入如下代碼:
sealed class MsgViewHolder(view:View):RecyclerView.ViewHolder(view){
}
class LeftViewHolder(view: View):MsgViewHolder(view){
val leftMsg:TextView=view.findViewById(R.id.leftMsg)
}
class RightViewHolder(view: View):MsgViewHolder(view){
val rightMsg:TextView=view.findViewById(R.id.rightMsg)
}這里我們定義了一個(gè)密封類MsgViewHolder,并讓他繼承自RecyclerView.ViewHolder,然后讓leftViewHolder和RightViewHolder繼承自MsgViewHolder。這樣就相當(dāng)于密封類MsgViewHolder只有兩個(gè)已知子類,因此在when語(yǔ)句中只要處理這兩種情況的條件分支即可。
修改MsgAdapter代碼,如下所示:
class MsgAdapter(val msgList:List<msg>):RecyclerView.Adapter<MsgViewHolder>(){
...
override fun onBindViewHolder(holder:MsgViewHolder,position:Int){
val msg=msgList[position]
when(holder){
is LeftViewHolder -> holder.leftMsg.text=msg.content
is RightViewHolder -> holder.rightMsg.text=msg.content
}
}
...
}這里我們將RecycleView.Adapter的泛型指定成剛剛定義的密封類MsgViewHolder,這樣onBindViewHolder()方法傳入的參數(shù)就變成了MsgViewHolder。然后我們只要在when語(yǔ)句當(dāng)中處理LeftViewHolder和RightViewHolder這兩種情況就可以了,else也不需要了。這種RecyclerView適配器的寫(xiě)法更加規(guī)范也更加推薦。
到此這篇關(guān)于kotlin延遲初始化和密封類詳細(xì)講解的文章就介紹到這了,更多相關(guān)Kotlin延遲初始化內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
5種Android數(shù)據(jù)存儲(chǔ)方式匯總
這篇文章主要為大家整理了5種Android數(shù)據(jù)存儲(chǔ)方式,列出了各存儲(chǔ)方式的優(yōu)缺點(diǎn),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-12-12
Android中傳遞對(duì)象的三種方法的實(shí)現(xiàn)
本篇文章主要介紹了Android中傳遞對(duì)象的三種方法的實(shí)現(xiàn),可以通過(guò)Bundle、Intent或者JSON字符串,有興趣的可以了解一下。2017-02-02
Android編程實(shí)現(xiàn)向SD卡寫(xiě)入數(shù)據(jù)的方法
這篇文章主要介紹了Android編程實(shí)現(xiàn)向SD卡寫(xiě)入數(shù)據(jù)的方法,涉及Android針對(duì)SD卡狀態(tài)判斷,文件及權(quán)限操作等相關(guān)技巧,需要的朋友可以參考下2016-04-04
Android實(shí)現(xiàn)垂直進(jìn)度條VerticalSeekBar
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)垂直進(jìn)度條VerticalSeekBar的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-07-07
Flutter應(yīng)用集成極光推送的實(shí)現(xiàn)示例
這篇文章主要介紹了Flutter應(yīng)用集成極光推送的實(shí)現(xiàn)示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-02-02
Android頁(yè)面中引導(dǎo)蒙層的使用方法詳解
這篇文章主要為大家詳細(xì)介紹了Android頁(yè)面中的引導(dǎo)蒙層使用方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-03-03
詳解Flutter Image組件如何處理圖片加載過(guò)程中的錯(cuò)誤
在Flutter中,Image組件可以通過(guò)監(jiān)聽(tīng)加載過(guò)程中的錯(cuò)誤來(lái)處理圖片加載過(guò)程中的錯(cuò)誤,本文小編將給大家詳細(xì)介紹了Flutter Image組件是如何處理圖片加載過(guò)程中的錯(cuò)誤,文中有詳細(xì)的代碼示例供大家參考,需要的朋友可以參下2023-10-10
Android編程實(shí)現(xiàn)禁止系統(tǒng)鎖屏與解鎖亮屏的方法
這篇文章主要介紹了Android編程實(shí)現(xiàn)禁止系統(tǒng)鎖屏與解鎖亮屏的方法,實(shí)例分析了Android關(guān)閉屏幕、鎖屏及解鎖屏幕的相關(guān)技巧,需要的朋友可以參考下2015-12-12

