Kotlin Reference: Object Expressions and Declarations

  有时候我们想对一个类进行些许修改,而又不想显式地声明一个新的子类。Java 使用匿名内部类(Anonymous Inner Class)的方式应对这种场景,Kotlin 使用对象表达式(Object Expression)对象声明(Object Declaration)进一步拓展了这个概念。

Object expressions

  要创建继承了某个(或多个)其他类型的匿名内部类,可以使用如下的方法:

[code lang=”kotlin”]window.addMouseListener(object : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) {
// …
}

override fun mouseEntered(e: MouseEvent) {
// …
}
})[/code]

  如果超类有构造器,则必须为其传递合适的构造器参数。多个超类可以在 object : 的冒号后面列出,并以逗号分隔:

[code lang=”kotlin”]open class A(x: Int) {
public open val y: Int = x
}

interface B {…}

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

  如果只是需要“一个对象”,而不需要它有超类,则可以简单地写为:

[code lang=”kotlin”]fun foo() {
val adHoc = object {
var x: Int = 0
var y: Int = 0
}
print(adHoc.x + adHoc.y)
}[/code]

  需要注意的是,匿名对象的类型仅在局部和 private 声明中有效,如果使用匿名对象作为 public 函数的返回类型或 public 属性的类型,则该函数和属性的实际类型为匿名对象声明的超类(如果匿名对象没有声明超类,则为 Any),此种情况下,加入到匿名对象的成员是不可访问的。

[code lang=”kotlin”]class C {
// Private function, so the return type is the anonymous object type
private fun foo() = object {
val x: String = “x”
}

// Public function, so the return type is Any
fun publicFoo() = object {
val x: String = “x”
}

fun bar() {
val x1 = foo().x // Works
val x2 = publicFoo().x // ERROR: Unresolved reference ‘x’
}
}[/code]

  对象表达式内部的代码可以访问外部作用域的成员,就像 Java 的匿名内部类一样。(但又不仅限于访问 final 变量,这一点与 Java 不同。)

[code lang=”kotlin”]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++
}
})
// …
}[/code]

Object declarations

  单例(Singleton) 是一种非常有用的模式,Kotlin(在 Scala之后)让声明单例变得很简单:

[code lang=”kotlin”]object DataProviderManager {
fun registerDataProvider(provider: DataProvider) {
// …
}

val allDataProviders: Collection
get() = // …
}[/code]

  这种使用 object 关键字后加名称的声明,称为对象声明(Object Declaration)。就像变量声明一样,对象声明不是表达式,不能放在赋值语句的右边。

  使用名称可以直接引用对象:

[code lang=”kotlin”]DataProviderManager.registerDataProvider(…)[/code]

  这些对象也可以具有超类:

[code lang=”kotlin”]object DefaultListener : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) {
// …
}

override fun mouseEntered(e: MouseEvent) {
// …
}
}[/code]

  注意:对象声明不能是局部的(如直接嵌套在函数中),但可以嵌套在其他对象声明或非内部类中。

Companion Objects

  类中的对象声明可以使用 companion 关键字标记,成为伴生对象(Companion Object):

[code lang=”kotlin”]class MyClass {
companion object Factory {
fun create(): MyClass = MyClass()
}
}[/code]

  伴生对象的成员可以简单地通过类名来引用:

[code lang=”kotlin”]val instance = MyClass.create()[/code]

  伴生对象的名称可以省略,此时会使用 Companion 作为名称:

[code lang=”kotlin”]class MyClass {
companion object {
}
}

val x = MyClass.Companion[/code]

  需要注意的是,虽然伴生对象的成员看上去像是其他语言中的静态成员,但是在运行时,伴生对象的成员仍是实际(单例)对象的实例成员,并且可以实现接口:

[code lang=”kotlin”]interface Factory {
fun create(): T
}

class MyClass {
companion object : Factory {
override fun create(): MyClass = MyClass()
}
}[/code]

  然而在 JVM 上可以使用 @JvmStatic 注解,让伴生对象的成员生成为真正的静态方法和字段,更多信息请见 Java Interoperability 一节。

Semantic difference between object expressions and declarations

  对象表达式和对象声明之间有一个非常重要的语义区别:

  • 对象表达式会在使用处立即执行(并初始化),
  • 对象声明的初始化是延迟的,仅在第一次被访问时初始化,
  • 伴生对象在相应的类被载入(解析)时初始化,对应 Java 的静态初始化器的机制。