有時候,我們需要創(chuàng)建一個對某個類做了輕微改動的類的對象,而不用為之顯式聲明新的子類。
Java 用匿名內(nèi)部類 處理這種情況。
Kotlin 用對象表達式和對象聲明對這個概念稍微概括了下。
要創(chuàng)建一個繼承自某個(或某些)類型的匿名類的對象,我們會這么寫:
window.addMouseListener(object : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) {
// ……
}
override fun mouseEntered(e: MouseEvent) {
// ……
}
})
如果超類型有一個構(gòu)造函數(shù),則必須傳遞適當?shù)臉?gòu)造函數(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ù)或?qū)傩缘膶嶋H類型
會是匿名對象聲明的超類型,如果你沒有聲明任何超類型,就會是 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”
}
}
就像 Java 匿名內(nèi)部類一樣,對象表達式中的代碼可以訪問來自包含它的作用域的變量。
(與 Java 不同的是,這不僅限于 final 變量。)
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++
}
})
// ……
}
單例模式是一種非常有用的模式,而 Kotlin(繼 Scala 之后)使單例聲明變得很容易:
object DataProviderManager {
fun registerDataProvider(provider: DataProvider) {
// ……
}
val allDataProviders: Collection<DataProvider>
get() = // ……
}
這稱為對象聲明。并且它總是在 object{: .keyword } 關(guān)鍵字后跟一個名稱。
就像變量聲明一樣,對象聲明不是一個表達式,不能用在賦值語句的右邊。
要引用該對象,我們直接使用其名稱即可:
DataProviderManager.registerDataProvider(……)
這些對象可以有超類型:
object DefaultListener : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) {
// ……
}
override fun mouseEntered(e: MouseEvent) {
// ……
}
}
注意:對象聲明不能在局部作用域(即直接嵌套在函數(shù)內(nèi)部),但是它們可以嵌套到其他對象聲明或非內(nèi)部類中。
類內(nèi)部的對象聲明可以用 companion{: .keyword } 關(guān)鍵字標記:
class MyClass {
companion object Factory {
fun create(): MyClass = MyClass()
}
}
該伴生對象的成員可通過只使用類名作為限定符來調(diào)用:
val instance = MyClass.create()
可以省略伴生對象的名稱,在這種情況下將使用名稱 Companion:
class MyClass {
companion object {
}
}
val x = MyClass.Companion
請注意,即使伴生對象的成員看起來像其他語言的靜態(tài)成員,在運行時他們
仍然是真實對象的實例成員,而且,例如還可以實現(xiàn)接口:
interface Factory<T> {
fun create(): T
}
class MyClass {
companion object : Factory<MyClass> {
override fun create(): MyClass = MyClass()
}
}
當然,在 JVM 平臺,如果使用 @JvmStatic 注解,你可以將伴生對象的成員生成為真正的
靜態(tài)方法和字段。更詳細信息請參見Java 互操作性一節(jié)
。
對象表達式和對象聲明之間有一個重要的語義差別: