【Android進(jìn)階】Kotlin筆記

Sealed Class 密封類(lèi)
如果想對(duì)能夠創(chuàng)建出的子類(lèi)做限制,可以使用密封類(lèi)。

下面一個(gè)例子是沒(méi)有使用密封類(lèi)的:

interface Expr
class Num(val value: Int) : Expr
class Sum(val left: Expr, val right: Expr) : Expr

fun eval(expr: Expr): Int {
    return when(expr){
        is Sum -> eval(expr.left) + eval(expr.right)
        is Num -> expr.value
        else -> throw IllegalArgumentException("Unknown Expression")
    }
}

這類(lèi)似于只支持加法的抽象語(yǔ)法樹(shù),Expr代表一個(gè)表達(dá)式,也就是語(yǔ)法樹(shù)里的一個(gè)節(jié)點(diǎn),同時(shí)Num代表數(shù)字節(jié)點(diǎn),它只可能是葉子,Sum代表加法節(jié)點(diǎn),不可能是葉子。

現(xiàn)在如果我們要實(shí)現(xiàn)eval函數(shù)來(lái)計(jì)算抽象語(yǔ)法樹(shù)的最終結(jié)果,我們發(fā)現(xiàn),始終需要一個(gè)else來(lái)收尾,因?yàn)镋xpr可能還有其他實(shí)現(xiàn)類(lèi),可能既不是Sum又不是Num,盡管代碼里根本沒(méi)有其他實(shí)現(xiàn)類(lèi)。

密封類(lèi)能解決這個(gè)問(wèn)題。

sealed class Expr {
    class Num(val value: Int) : Expr()
    class Sum(val left: Expr, val right: Expr) : Expr()
}


fun eval(expr: Expr): Int {
    return when(expr){
        is Expr.Sum -> eval(expr.left) + eval(expr.right)
        is Expr.Num -> expr.value
    }
}

密封類(lèi)表明了該類(lèi)不可能有除了Num和Sum之外的其他子類(lèi),所以編譯器可以發(fā)現(xiàn)我們when中的代碼是無(wú)懈可擊的,自然不用一個(gè)額外的else。

類(lèi)委托
Java中有一套設(shè)計(jì)模式就是委托模式,就是指編寫(xiě)一個(gè)類(lèi),但它不提供實(shí)現(xiàn),所有的功能都會(huì)委托給另一個(gè)類(lèi)實(shí)現(xiàn),在必要的時(shí)候?qū)︻?lèi)進(jìn)行增強(qiáng)。Java后面的代理、動(dòng)態(tài)代理技術(shù)全部都是基于委托實(shí)現(xiàn)的,可以說(shuō)它是Java世界的一個(gè)支柱。

Kotlin默認(rèn)支持委托,不像Java,要么用IDE生成一大堆代碼,要么在編譯期使用其他動(dòng)態(tài)代理工具生成,Kotlin默認(rèn)提供了by關(guān)鍵字。

下面的類(lèi)繼承自MutableCollection,但它完全不存儲(chǔ)數(shù)據(jù),而是通過(guò)by委托給innerSet。

class CountingSet<T> (
    val innerSet: MutableCollection<T> = HashSet<T>()
) : MutableCollection<T> by innerSet{
    var objectsAdded = 0


    override fun add(element: T): Boolean {
        objectsAdded++
        return innerSet.add(element)
    }

    override fun addAll(elements: Collection<T>): Boolean {
        objectsAdded += elements.size
        return innerSet.addAll(elements)
    }

}



fun main(){
    val set = CountingSet<Int>()
    set.add(10)
    set.addAll(listOf(1,2,4,15,5,3))
    set.remove(10)
    println(set.objectsAdded)
}

比較常用的就是委托類(lèi)作為構(gòu)造器參數(shù)傳入,Java中比較常見(jiàn)的就是基于委托的IO流,我們經(jīng)常這樣寫(xiě):

new BufferedInputStream(new FileInputStream(...));

這里BufferedInputStream并不會(huì)自己實(shí)現(xiàn)InputStream的讀取功能,而是委托給FileInputStream并對(duì)它的功能進(jìn)行增強(qiáng)(通過(guò)建立緩沖區(qū))。

我們上面編寫(xiě)的類(lèi)也是,你可以調(diào)用CountingSet傳入不同的Collection實(shí)現(xiàn),不同的是我們提供了一個(gè)默認(rèn)值。

除了使用構(gòu)造器參數(shù),還可以直接新建一個(gè)類(lèi)委托,因?yàn)橛袝r(shí)候我們就想讓它委托同一個(gè)類(lèi),不想讓用戶自己抉擇。

class MySet<T> () : 
    MutableCollection<T> by HashSet<T>(){

}

屬性委托
Jetpack Compose中有一個(gè)記錄狀態(tài)并自動(dòng)更新UI的東西,就是var value by remember,這種監(jiān)測(cè)數(shù)據(jù)更新并自動(dòng)刷新UI的東西在如今數(shù)據(jù)驅(qū)動(dòng)的框架中并不少見(jiàn)。Jetpack Compose就是通過(guò)屬性委托來(lái)實(shí)現(xiàn)的數(shù)據(jù)監(jiān)測(cè)。

class Remember{
    lateinit var name: String
    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
//        return name
        return name
    }
    operator fun setValue(thisRef: Any?, property: KProperty<*>,value: String) {
//        name = value
        println("${property.name} is changed!!!")
        name = value
    }

}

fun main(){
    var name: String by Remember()
    name = "ASDFASDF"
    println(name)
}

/*
name is changed!!!
ASDFASDF
*/

被委托的類(lèi)應(yīng)該實(shí)現(xiàn)一個(gè)getValue和setValue方法,委托方的變量不再存儲(chǔ)值,而是由被委托的類(lèi)提供存儲(chǔ)功能。

我們接下來(lái)編寫(xiě)一個(gè)懶加載的屬性委托,就是第一次訪問(wèn)屬性時(shí)才為屬性賦值

class LazyDelegate<T>(private val compute: ()->T){
    var t: T? = null
    operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
        if (t == null) t = compute()
        return t!!
    }
    operator fun setValue(thisRef: Any?, property: KProperty<*>,value: T) {
        t = value
    }
}
fun <T> lazy(compute: ()->T) = LazyDelegate<T>(compute)

fun main(){
    var name: String by lazy {
        "HelloWorld"
    }

    println(name)
}

這一次我們提供了一個(gè)lazy方法,Lazy方法會(huì)返回我們的委托人LazyDelegate,因?yàn)镵otlin官方就為一些自帶的委托封裝了方法,可能是Kotlin社區(qū)慣用的編碼規(guī)范,確實(shí),這樣好看一些,而且Jetpack Compose中的remember實(shí)際上也是這樣寫(xiě)的。

然后,我們還運(yùn)用了泛型和lambda表達(dá)式,lambda用于返回一個(gè)值,一般使用懶加載的時(shí)候,這個(gè)lambda表達(dá)式都會(huì)是一個(gè)很復(fù)雜并且可能并不常用的運(yùn)算,所以這樣如果這個(gè)值如果沒(méi)被需要,懶加載就不會(huì)執(zhí)行。泛型用于支持全部類(lèi)型的值。

伴生對(duì)象
Java中經(jīng)常會(huì)使用靜態(tài)工廠方法來(lái)構(gòu)造對(duì)象,這是因?yàn)殪o態(tài)工廠方法比構(gòu)造器更加適用于處理那些很多屬性可以不在構(gòu)造時(shí)提供的類(lèi)。靜態(tài)工廠方法更加具有可讀性。Kotlin根本沒(méi)有靜態(tài)這一說(shuō),Kotlin代替靜態(tài)的辦法一個(gè)是object,一個(gè)是頂層函數(shù)。但這倆都不適用于靜態(tài)工廠,因?yàn)殪o態(tài)工廠經(jīng)常要訪問(wèn)類(lèi)中的私有成員。

伴生對(duì)象是用來(lái)干這些的。

class Person private constructor(
    name: String?, 
    age: Int?, 
    address: String?
){
    companion object {
        fun fromNameAndAge(name: String, age: Int): Person = Person(name,age,null)
        fun fromName(name: String): Person = Person(name,null,null)
        fun fromNameAndAddress(name: String, address: String): Person = Person("Lucy",null,address)
        fun newPerson(name: String, age: Int, address: String): Person = Person(name,age,address)
    }
}

fun main(){
    Person.fromNameAndAge("Lucy",12)
}

這對(duì)于Builder模式同樣適用,對(duì)于絕大多數(shù)需要和類(lèi)中私有成員進(jìn)行交互的地方,都適用。

但是,別忘了Kotlin中的命名參數(shù),上面的例子本可以用命名參數(shù)更加方便的解決。

class Person constructor(
    name: String, 
    age: Int? = null, 
    address: String? = null
)

fun main(){
    Person(name = "Lucy", age = 12)
}

當(dāng)然伴生對(duì)象可以命名

class Person constructor(
    name: String, 
    age: Int? = null, 
    address: String? = null
){
    companion object Loader{
        fun fromJson(json: String): Person{ ... }
    }
}

fun main(){
    Person.Loader.fromJson()
}

伴生對(duì)象也可以有擴(kuò)展函數(shù),這是因?yàn)橄裆厦娴腖oader這種伴生對(duì)象和類(lèi)中的邏輯關(guān)系不大,分離到外部可以實(shí)現(xiàn)關(guān)注點(diǎn)分離。

class Person constructor(
    name: String, 
    age: Int? = null, 
    address: String? = null
){
    companion object Loader{}
}

fun Person.Loader.fromJson(json: String): Person {
    ...
}

fun main(){
    Person.Loader.fromJson()
}

如果是沒(méi)有名字的伴生對(duì)象,也可以

fun Person.Companion.fromJson(json: String): Person{
    ...
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 擴(kuò)展 擴(kuò)展方法 Kotlin支持?jǐn)U展方法和擴(kuò)展屬性。語(yǔ)法:被擴(kuò)展的類(lèi)/接口名.方法名() 父類(lèi)不能使用子類(lèi)的擴(kuò)展方...
    AilurusFulgens閱讀 449評(píng)論 0 1
  • 這篇屬于Kotlin進(jìn)階,講了與類(lèi)相關(guān)內(nèi)容,主要涉及到:類(lèi)定義、類(lèi)屬性和函數(shù)、類(lèi)繼承、抽象類(lèi)和接口、委托與代理、單...
    海_3efc閱讀 496評(píng)論 0 1
  • 郭神的Android第一行代碼更新了,聽(tīng)到消息的我立馬就下單了,拿到書(shū)就開(kāi)始如饑似渴地翻閱著.寫(xiě)的特別棒,學(xué)到了很...
    瀟風(fēng)寒月閱讀 560評(píng)論 0 0
  • Kotlin進(jìn)階學(xué)習(xí)筆記 從源碼分析學(xué)習(xí)Kotlin,知其然、知其所以然。 通用基礎(chǔ)語(yǔ)法學(xué)習(xí)Kotlin官網(wǎng)[ht...
    志威夢(mèng)閱讀 607評(píng)論 0 0
  • Kotlin筆記 要理解Java與Kotlin的區(qū)別,就要從最根本的上來(lái)理解。Java是解釋型語(yǔ)言(邊解釋成二進(jìn)制...
    FFFSnow閱讀 1,176評(píng)論 0 0

友情鏈接更多精彩內(nèi)容