有時候我們需要創建與某個類稍微不一樣的一個對象,但不用爲此顯示地聲明一個子類。Java使用匿名內部類來處理這種情況,Kotlin用對象表達式和對象聲明略微概括了這個概念。
對象表達式
創建一個繼承自某個(或某些)類型的匿名類對象:
window.addMouseListener(object : MouseAdapter(){
override fun mouseClicked(e: MouseEvent){...}
override fun mouseEntered(e: MouseEvnent){...}
})
如果父類有構造方法,那麼創建的時候必須傳參數。如果有多個父類,可以用逗號隔開:
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)
}
注意匿名對象只能在本地和私有聲明中用作類型。如果使用一個匿名對象作爲public方法或public屬性的返回值,那麼該方法或屬性的返回值的實際類型將會是匿名對象父類的聲明類型,或者是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 // Works
val x2 = publicFoo().x // ERROR:調用會提示錯誤
}
}
因爲publicFoo()
方法的返回值類型是Any
,所以無法調用x
屬性。
和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中聲明單例非常簡單:
object DataProviderManager {
fun registerDataProvider(provider: DataProvider) {
// ...
}
val allDataProviders: Collection<DataProvider>
get() = // ...
}
以上就是對象聲明,同時也是一個單例。在object關鍵字後面總是要有一個名稱。就像變量聲明一樣,對象聲明不是一個表達式,不能用在賦值語句的右邊。對象聲明可以有屬性,方法等等,但是不能有構造方法。
對象聲明的初始化是線程安全的。
要引用該對象,直接使用它的名稱即可:
DataProviderManager.registerDataProvider(...)
對象聲明可以有父類:
object DefaultListener : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) { ... }
override fun mouseEntered(e: MouseEvent) { ... }
}
注意:對象聲明不能聲明爲局部(比如直接嵌套在一個方法內)。但是它們能嵌套在其它對象聲明中或者非內部類中
伴生對象
類內部的對象聲明可以使用companion
關鍵字來標記:
class MyClass {
companion object Factory {
fun create(): MyClass = MyClass()
}
}
伴生對象的成員可以使用類名作爲限定符來調用:
val instance = MyClass.create()
伴生對象的名稱可以被忽略:
class MyClass {
companion object {
var name = "name"
}
}
//以下兩種方式都可以調用伴生對象的成員
val x = MyClass.name
val x = MyClass.Companion.name
注意:儘管伴生對象的成員看起來像靜態成員,在運行時它們仍然是真實對象的實例成員,並且能夠實現接口:
interface Factory<T> {
fun create(): T
}
class MyClass {
companion object : Factory<MyClass> {
override fun create(): MyClass = MyClass()
}
}
然而,在JVM平臺上,如果使用@JvmStatic註釋,可以把伴生對象的成員聲明爲真正的靜態方法和字段,這個之後再細說。
對象表達式和對象聲明的語義差別
- 對象表達式會被在使用的地方立即執行(初始化);
- 對象聲明是在第一次被訪問的時候懶初始化的;
- 伴生對象是在相應的類被加載(解析)的時候初始化的,匹配Java靜態初始化器。