Kotlin對象表達式與對象聲明、委托、委托屬性

一、對象表達式

要創(chuàng)建?個繼承自某個(或某些)類型的匿名類的對象:

//對象表達式中的代碼可以訪問來自包含它的作用域的變量
fun countClicks(window: JComponent) {
    var clickCount = 0
    var enterCount = 0
    window.addMouseListener(object : MouseAdapter() {
        override fun mouseClicked(e: MouseEvent) {
            clickCount++
        }

        override fun mouseEntered(e: MouseEvent) {
            enterCount++
        }
    })// ……
}

如果超類型有?個構造函數(shù),則必須傳遞適當?shù)臉嬙旌瘮?shù)參數(shù)給它。多個超類型可以由跟在冒號后面的逗號分隔的列表指定:

open class A(x: Int) {
    public open val y: Int = x
}

interface B { /*……*/ }

val ab: A = object : A(1), B {
    override val y = 15
}

如果我們只需要“?個對象而已”,并不需要特殊超類型,那么我們可以簡單地寫:

fun foo() {
    val adHoc = object {
        var x: Int = 0 
        var y: Int = 0
    } 
    print (adHoc.x + adHoc.y)
}

匿名對象可以用作只在本地和私有作用域中聲明的類型。如果你使用匿名對象作為公有函數(shù)的返回類型或者用作公有屬性的類型,那么該函數(shù)或屬性的實際類型會是匿名對象聲明的超類型,如果你沒有聲明任何超類型,就會是 Any ,所以在匿名對象中添加的成員將無法訪問。

class C {
    // 私有函數(shù),所以其返回類型是匿名對象類型 
    private fun foo() = object {
        val x: String = "x"
    }

    // 公有函數(shù),所以其返回類型是Any 
    fun publicFoo() = object {
        val x: String = "x"
    }

    fun bar() {
        val x1 = foo().x // 沒問題 
        val x2 = publicFoo().x // 錯誤:未能解析的引用“x” 
    }
}

二、對象聲明

總是在 object 關鍵字后跟?個名稱。對象聲明的初始化過程是線程安全的并且在首次訪問時進行。
對象聲明不能在局部作用域(即直接嵌套在函數(shù)內部),但是它們可以嵌套到其他對象聲明或非內部類中。

1.伴生對象

  • companion 關鍵字標記
  • 伴生對象的成員可通過只使用類名作為限定符來調用
  • 省略伴生對象的名稱,在這種情況下將使用名稱 Companion
  • 其類的名稱可用作對該類的伴生對象的引用
  • 伴生對象的成員是真實對象的實例成員,還可實現(xiàn)接口
  • 使用@JvmStatic 注解,你可以將伴生對象的成員生成為真正的靜態(tài)方法和字段
interface Factory<T> {
    fun create(): T
}

class MyClass {
    companion object : Factory<MyClass> {
        override fun create(): MyClass = MyClass()
    }
}

val f: Factory<MyClass> = MyClass
val companionClassA = MyClass
val companionClassB = MyClass.Companion

2.對象表達式和對象聲明之間的語義差異

  • 對象表達式是在使用他們的地方立即執(zhí)行(及初始化)的
  • 對象聲明是在第?次被訪問到時延遲初始化的
  • 伴生對象的初始化是在相應的類被加載(解析)時,與 Java 靜態(tài)初始化器的語義相匹配

三、類型別名

類型別名為現(xiàn)有類型提供替代名稱。如果類型名稱太長,你可以另外引入較短的名稱,并使用新的名稱替代原類型名。類型別名不會引入新類型。它們等效于相應的底層類型

//縮減集合類型:
typealias NodeSet = Set<Network.Node> 
typealias FileTable<K> = MutableMap<K, MutableList<File>>

//函數(shù)類型提供另外的別名:
typealias MyHandler = (Int, String, Any) -> Unit 
typealias Predicate<T> = (T) -> Boolean

//嵌套類和內部類創(chuàng)建新名稱:
class A {
    class Inner
}

class B {
    inner class Inner
}
typealias AInner = A.Inner
typealias BInner = B.Inner

fun foo(p: Predicate<Int>) = p(42)
fun main() {
    val f: (Int) -> Boolean = { it > 0 }
    println(foo(f)) // 輸出 "true"

    val p: Predicate<Int> = { it > 0 }
    println(listOf(1, -2).filter(p)) // 輸出 "[1]"
}

四、委托

1.由委托實現(xiàn)

委托模式已經證明是實現(xiàn)繼承的?個很好的替代方式,而 Kotlin 可以零樣板代碼地原生支持它。Derived 類 可以通過將其所有公有成員都委托給指定對象來實現(xiàn)?個接口Base:

interface Base {
    fun print()
}

class BaseImpl(val x: Int) : Base {
    override fun print() {
        print(x)
    }
}

class Derived(b: Base) : Base by b

fun main() {
    val b = BaseImpl(10) 
    Derived (b).print()
}

Derived 的超類型列表中的 by-子句表式 b 將會在 Derived 中內部存儲,并且編譯器將生成轉發(fā)給 b 的所有 Base 的方法。

2.覆蓋由委托實現(xiàn)的接口成員

覆蓋符合預期:編譯器會使用 override 覆蓋的實現(xiàn)而不是委托對象中的。重寫的成員不會在委托對象的成員中調用 ,委托對象的成員只能訪問其自身對接口成員

interface BaseA {
    val message: String
    fun print()
}

class BaseImpl(x: Int) : BaseA {
    override val message = "BaseImpl: x = $x"
    override fun print() {
        println(message)
    }
}

class DerivedA(b: BaseA) : BaseA by b {
    // 在 b 的 `print` 實現(xiàn)中不會訪問到這個屬性 
    override val message = "Message of Derived"
}

fun main() {
    val b = BaseImpl(10)
    val derived = DerivedA(b)
    derived.print()
    println(derived.message)
}

3.委托屬性

有?些常見的屬性類型,雖然我們可以在每次需要的時候手動實現(xiàn)它們,但是如果能夠為大家把他們只實現(xiàn)?次并放入?個庫會更好。例如:

  • 延遲屬性(lazy properties):其值只在首次訪問時計算;
  • 可觀察屬性(observable properties): 監(jiān)聽器會收到有關此屬性變更的通知;
  • 把多個屬性儲存在?個映射(map)中,而不是每個存在單獨的字段中。

語法是:val/var <屬性名>: <類型> by <表達式> 。在 by 后面的表達式是該委托,因為屬性對應的 get()(與 set() )會被委托給它的 getValue() 與 setValue() 方法。屬性的委托不必實現(xiàn)任何的接口,但是需要提供?個 getValue() 函數(shù)(與 setValue() ——對于 var 屬性)

class Example {
    var p: String by Delegate()
}

class Delegate {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        return "$thisRef, thank you for delegating '${property.name}' to me!"
    }

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
        println("$value has been assigned to '${property.name}' in $thisRef.")
    }
}

fun main() {
    val e = Example()
    println(e.p)
    e.p = "NEW"
}

輸出:
Example@33a17727, thank you for delegating ‘p’ to me!
NEW has been assigned to ‘p’ in Example@33a17727.

4.標準委托:延遲屬性 Lazy

lazy() 是接受?個 lambda 并返回?個 Lazy <T> 實例的函數(shù),返回的實例可以作為實現(xiàn)延遲屬性的委托: 第?次調? get() 會執(zhí)?已傳遞給 lazy() 的 lambda 表達式并記錄結果,后續(xù)調? get() 只是返回記 錄的結果。

val lazyValue: String by lazy {
    println("MainActivity computed!")
    return@lazy "Hello"    //return@lazy 可以省略
}

fun main() {
    println(lazyValue)
    println(lazyValue)
}

lazy 屬性的求值是同步鎖的(synchronized):該值只在?個線程中計算,并且所有線程會看到相同的值。
LazyThreadSafetyMode.PUBLICATION設置多個線程可以同步初始化委托。如果確定初始化將總是發(fā)生在與屬性使用位于相同的線程,可以使用LazyThreadSafetyMode.NONE 模式:它不會有任何線程安全的保證以及相關的開銷。

5.標準委托:可觀察屬性 Observable

Delegates.observable() 接受兩個參數(shù):初始值與修改時處理程序(handler)。每當我們給屬性賦值時會 調?該處理程序(在賦值后執(zhí)?)。它有三個參數(shù):被賦值的屬性、舊值與新值:

class User {
    var name: String by Delegates.observable("<no name>") { prop, old, new ->
        println("$old -> $new")
    }
}

fun main() {
    val user = User()
    user.name = "first"
    user.name = "second"
}

截獲賦值并“否決”它們,那么使用 vetoable() 取代 observable() 。在屬性被賦新值生效之前會調用傳遞給 vetoable 的處理程序。

6.委托給另?個屬性

?個屬性可以把它的 getter 與 setter 委托給另?個屬性。這種委托對于頂層和類的屬性(成員和擴展)都可用。該委托屬性可以為:

  • 頂層屬性
  • 同?個類的成員或擴展屬性
  • 另?個類的成員或擴展屬性
    為將?個屬性委托給另?個屬性,應在委托名稱中使用恰當?shù)?:: 限定符
class ClassWithDelegate {
    val anotherClassInt = 0
}

var topLevelInt: Int = 0

class MyClass(var memberInt: Int, val anotherClassInstance: ClassWithDelegate) {
    var delegatedToMember: Int by this::memberInt
    var delegatedToTopLevel: Int by ::topLevelInt
    val delegatedToAnotherClass: Int by anotherClassInstance::anotherClassInt
}

var MyClass.extDelegated: Int by ::topLevelInt

當想要以?種向后兼容的方式重命名?個屬性的時候:引入?個新的屬性、使用 @Deprecated 注解來注解舊的屬性、并委托其實現(xiàn)。

class MyCla {
    var newName: Int = 0

    @Deprecated("Use 'newName' instead", ReplaceWith("newName"))
    var oldName: Int by this::newName
}

fun main() {
    val myClass = MyCla() // 通知:'oldName: Int' is deprecated.
    // Use 'newName' instead
    myClass.oldName = 42
    println(myClass.newName) //42
}

7.將屬性儲存在映射中

?個常見的用例是在?個映射(map)里存儲屬性的值。這經常出現(xiàn)在像解析 JSON 或者做其他“動態(tài)”事情的應用中。在這種情況下,你可以使用映射實例自身作為委托來實現(xiàn)委托屬性。

class User(map: Map<String, Any?>) {
    val name: String by map
    val age: Int by map
}

val user = User(mapOf("name" to "John Doe", "age" to 25))

fun main() {
    println(user.name)  // Prints "John Doe"
    println(user.age)   // Prints 25
}

//var屬性,需要把只讀的 Map 換成 MutableMap
class MutableUser(val map: MutableMap<String, Any?>) {
    var name: String by map
    var age: Int by map
}

8.局部委托屬性

可以將局部變量聲明為委托屬性,如by lazy?個局部變量惰性初始化。

9.屬性委托要求

對于?個只讀屬性(即 val 聲明的),委托必須提供?個操作符函數(shù) getValue() ,該函數(shù)具有以下參數(shù):

  • thisRef —— 必須與屬性所有者類型(對于擴展屬性——指被擴展的類型)相同或者是其超類型。
  • property —— 必須是類型 KProperty<*> 或其超類型。
    getValue() 必須返回與屬性相同的類型(或其子類型)。

對于?個可變屬性(即 var 聲明的),委托必須額外提供?個操作符函數(shù) setValue() ,該函數(shù)具有以下參數(shù):

  • thisRef —— 必須與屬性所有者類型(對于擴展屬性——指被擴展的類型)相同或者是其超類型。
  • property —— 必須是類型 KProperty<*> 或其超類型。
  • value — 必須與屬性類型相同(或者是其超類型)。

getValue() 或/與 setValue() 函數(shù)可以通過委托類的成員函數(shù)提供或者由擴展函數(shù)提供。當你需要委托屬性到原本未提供的這些函數(shù)的對象時后者會更便利。兩函數(shù)都需要用 operator 關鍵字來進行標記。

翻譯規(guī)則

在每個委托屬性的實現(xiàn)的背后,Kotlin 編譯器都會生成輔助屬性并委托給它。例如,對于屬性 prop,生成隱藏 屬性 prop$delegate ,而訪問器的代碼只是簡單地委托給這個附加屬性:

class C {
    var prop: Type by MyDelegate()
}

// 這段是由編譯器?成的相應代碼:
class C {
    private val prop$delegate = MyDelegate()
    var prop: Type get() = prop$delegate.getValue(this, this::prop)
    set(value: Type) = prop$delegate.setValue(this, this::prop, value)
}

編譯器在參數(shù)中提供了關于 prop 的所有必要信息:第?個參數(shù) this 引用到外部類 C 的實例而 this::prop 是 KProperty 類型的反射對象,該對象描述 prop 自身

提供委托

通過定義 provideDelegate 操作符,可以擴展創(chuàng)建屬性實現(xiàn)所委托對象的邏輯。如果 by 右側所使?的對 象將 provideDelegate 定義為成員或擴展函數(shù),那么會調?該函數(shù)來創(chuàng)建屬性委托實例。

class ResourceDelegate<T> : ReadOnlyProperty<MyUI, T> {
    override fun getValue(thisRef: MyUI, property: KProperty<*>): T {
        ...
    }
}

class ResourceLoader<T>(id: ResourceID<T>) {
    operator fun provideDelegate(thisRef: MyUI, prop: KProperty<*>): ReadOnlyProperty<MyUI, T> {
        checkProperty(thisRef, prop.name)
        // 創(chuàng)建委托 
        return ResourceDelegate()
    }

    private fun checkProperty(thisRef: MyUI, name: String) {
        ……
    }
}

class MyUI {
    fun <T> bindResource(id: ResourceID<T>): ResourceLoader<T> {
        ……
    }

    val image by bindResource(ResourceID.image_id)
    val text by bindResource(ResourceID.text_id)
}

provideDelegate 的參數(shù)與 getValue 相同:

  • thisRef —— 必須與 屬性所有者 類型(對于擴展屬性——指被擴展的類型)相同或者是它的超類型;
  • property —— 必須是類型 KProperty<*> 或其超類型。
    在創(chuàng)建 MyUI 實例期間,為每個屬性調用provideDelegate 方法,并立即執(zhí)行必要的驗證
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內容