一、對象表達式
要創建?個繼承自某個(或某些)類型的匿名類的對象:
//對象表達式中的代碼可以訪問來自包含它的作用域的變量
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++
}
})// ……
}
如果超類型有?個構造函數,則必須傳遞適當的構造函數參數給它。多個超類型可以由跟在冒號后面的逗號分隔的列表指定:
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)
}
匿名對象可以用作只在本地和私有作用域中聲明的類型。如果你使用匿名對象作為公有函數的返回類型或者用作公有屬性的類型,那么該函數或屬性的實際類型會是匿名對象聲明的超類型,如果你沒有聲明任何超類型,就會是 Any ,所以在匿名對象中添加的成員將無法訪問。
class C {
// 私有函數,所以其返回類型是匿名對象類型
private fun foo() = object {
val x: String = "x"
}
// 公有函數,所以其返回類型是Any
fun publicFoo() = object {
val x: String = "x"
}
fun bar() {
val x1 = foo().x // 沒問題
val x2 = publicFoo().x // 錯誤:未能解析的引用“x”
}
}
二、對象聲明
總是在 object 關鍵字后跟?個名稱。對象聲明的初始化過程是線程安全的并且在首次訪問時進行。
對象聲明不能在局部作用域(即直接嵌套在函數內部),但是它們可以嵌套到其他對象聲明或非內部類中。
1.伴生對象
- companion 關鍵字標記
- 伴生對象的成員可通過只使用類名作為限定符來調用
- 省略伴生對象的名稱,在這種情況下將使用名稱 Companion
- 其類的名稱可用作對該類的伴生對象的引用
- 伴生對象的成員是真實對象的實例成員,還可實現接口
- 使用@JvmStatic 注解,你可以將伴生對象的成員生成為真正的靜態方法和字段
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.對象表達式和對象聲明之間的語義差異
- 對象表達式是在使用他們的地方立即執行(及初始化)的
- 對象聲明是在第?次被訪問到時延遲初始化的
- 伴生對象的初始化是在相應的類被加載(解析)時,與 Java 靜態初始化器的語義相匹配
三、類型別名
類型別名為現有類型提供替代名稱。如果類型名稱太長,你可以另外引入較短的名稱,并使用新的名稱替代原類型名。類型別名不會引入新類型。它們等效于相應的底層類型
//縮減集合類型:
typealias NodeSet = Set<Network.Node>
typealias FileTable<K> = MutableMap<K, MutableList<File>>
//函數類型提供另外的別名:
typealias MyHandler = (Int, String, Any) -> Unit
typealias Predicate<T> = (T) -> Boolean
//嵌套類和內部類創建新名稱:
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.由委托實現
委托模式已經證明是實現繼承的?個很好的替代方式,而 Kotlin 可以零樣板代碼地原生支持它。Derived 類 可以通過將其所有公有成員都委托給指定對象來實現?個接口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 中內部存儲,并且編譯器將生成轉發給 b 的所有 Base 的方法。
2.覆蓋由委托實現的接口成員
覆蓋符合預期:編譯器會使用 override 覆蓋的實現而不是委托對象中的。重寫的成員不會在委托對象的成員中調用 ,委托對象的成員只能訪問其自身對接口成員
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` 實現中不會訪問到這個屬性
override val message = "Message of Derived"
}
fun main() {
val b = BaseImpl(10)
val derived = DerivedA(b)
derived.print()
println(derived.message)
}
3.委托屬性
有?些常見的屬性類型,雖然我們可以在每次需要的時候手動實現它們,但是如果能夠為大家把他們只實現?次并放入?個庫會更好。例如:
- 延遲屬性(lazy properties):其值只在首次訪問時計算;
- 可觀察屬性(observable properties): 監聽器會收到有關此屬性變更的通知;
- 把多個屬性儲存在?個映射(map)中,而不是每個存在單獨的字段中。
語法是:val/var <屬性名>: <類型> by <表達式> 。在 by 后面的表達式是該委托,因為屬性對應的 get()(與 set() )會被委托給它的 getValue() 與 setValue() 方法。屬性的委托不必實現任何的接口,但是需要提供?個 getValue() 函數(與 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> 實例的函數,返回的實例可以作為實現延遲屬性的委托: 第?次調? get() 會執?已傳遞給 lazy() 的 lambda 表達式并記錄結果,后續調? get() 只是返回記 錄的結果。
val lazyValue: String by lazy {
println("MainActivity computed!")
return@lazy "Hello" //return@lazy 可以省略
}
fun main() {
println(lazyValue)
println(lazyValue)
}
lazy 屬性的求值是同步鎖的(synchronized):該值只在?個線程中計算,并且所有線程會看到相同的值。
LazyThreadSafetyMode.PUBLICATION設置多個線程可以同步初始化委托。如果確定初始化將總是發生在與屬性使用位于相同的線程,可以使用LazyThreadSafetyMode.NONE 模式:它不會有任何線程安全的保證以及相關的開銷。
5.標準委托:可觀察屬性 Observable
Delegates.observable() 接受兩個參數:初始值與修改時處理程序(handler)。每當我們給屬性賦值時會 調?該處理程序(在賦值后執?)。它有三個參數:被賦值的屬性、舊值與新值:
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 委托給另?個屬性。這種委托對于頂層和類的屬性(成員和擴展)都可用。該委托屬性可以為:
- 頂層屬性
- 同?個類的成員或擴展屬性
- 另?個類的成員或擴展屬性
為將?個屬性委托給另?個屬性,應在委托名稱中使用恰當的 :: 限定符
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 注解來注解舊的屬性、并委托其實現。
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)里存儲屬性的值。這經常出現在像解析 JSON 或者做其他“動態”事情的應用中。在這種情況下,你可以使用映射實例自身作為委托來實現委托屬性。
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 聲明的),委托必須提供?個操作符函數 getValue() ,該函數具有以下參數:
- thisRef —— 必須與屬性所有者類型(對于擴展屬性——指被擴展的類型)相同或者是其超類型。
- property —— 必須是類型 KProperty<*> 或其超類型。
getValue() 必須返回與屬性相同的類型(或其子類型)。
對于?個可變屬性(即 var 聲明的),委托必須額外提供?個操作符函數 setValue() ,該函數具有以下參數:
- thisRef —— 必須與屬性所有者類型(對于擴展屬性——指被擴展的類型)相同或者是其超類型。
- property —— 必須是類型 KProperty<*> 或其超類型。
- value — 必須與屬性類型相同(或者是其超類型)。
getValue() 或/與 setValue() 函數可以通過委托類的成員函數提供或者由擴展函數提供。當你需要委托屬性到原本未提供的這些函數的對象時后者會更便利。兩函數都需要用 operator 關鍵字來進行標記。
翻譯規則
在每個委托屬性的實現的背后,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)
}
編譯器在參數中提供了關于 prop 的所有必要信息:第?個參數 this 引用到外部類 C 的實例而 this::prop 是 KProperty 類型的反射對象,該對象描述 prop 自身
提供委托
通過定義 provideDelegate 操作符,可以擴展創建屬性實現所委托對象的邏輯。如果 by 右側所使?的對 象將 provideDelegate 定義為成員或擴展函數,那么會調?該函數來創建屬性委托實例。
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)
// 創建委托
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 的參數與 getValue 相同:
- thisRef —— 必須與 屬性所有者 類型(對于擴展屬性——指被擴展的類型)相同或者是它的超類型;
- property —— 必須是類型 KProperty<*> 或其超類型。
在創建 MyUI 實例期間,為每個屬性調用provideDelegate 方法,并立即執行必要的驗證