Kotlin中的object和companion object关键字

一. object

1. object 表达式

可以创建匿名内部类

window.addMouseListener(object : MouseAdapter() {
    override fun mouseClicked(e: MouseEvent) {
        // ...
    }
    override fun mouseEntered(e: MouseEvent) {
        // ...
    }
})   

通过object 创建的匿名内部类可以继承父类,实现多个接口,如果父类默认构造方法有参数需要传递合适的参数

open class A(x: Int) {
    public open val y: Int = x
}
interface B {...}
 
val ab: A = object : A(1), B {
    override val y = 12
}

有时候需要一个仅仅是一个对象的变量,不继承其他类,也可以直接用object

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

需要注意的是,这种匿名的对象只能被用在局部或者private声明,如果将匿名对象作为public方法的返回值或者作为一个public的属性,那么【这个方法的返回值或属性的类型】的真实值被置为匿名对象的父类类型,如果没有继承就是Any,那么被声明在匿名对象内部的变量便不可访问。

class C {
    //private方法,返回值是匿名对象的类型
    private fun foo() = object {
        val x: Int = 233
    }
    //public方法,返回值是 Any
    fun publicFoo() = object {
        val x: Int = 666
    }
    fun bar() {
        val x1 = foo().x
        val x2 = publicFoo().x  //会发生错误,找不到这个变量
    }
}

同Java一样object声明的匿名内部类可以访问局部变量,但是Java只能访问final修饰的,object则没有这个限制

fun countClicks(window: JCompenet) {
    var clickCount = 0
    var enterCount = 0
 
    window.addMouseListener(object: MouseAdapter) {
        override fun mouseClicked(e: Event) {
            clickCount++
        }
        override fun mouseEntered(e: Event) {
            enterCount++
        }
    }
}

2. object 声明

当用object 声明一个类时,表明这个类是一个单例类,这比在Java中声明一个单例类要简单得多

object DataProviderManager {
    fun registerDataProvider(provider: DataProvider) {
        //...
    }
    val allDataProviders: Collection
        get() = //...
}

在使用时,可以直接使用类名作为对象来调用方法

DataProviderManaer.registerDataProvider(...)

这种单例类也可以继承其他类

object DefaultListener : MouseAdapter() {
    override fun mouseClicked(e: Event) {...}
    override fun mouseEntered(e: Event) {...}
}

注意object声明不能用在方法和inner内部类中,但是能嵌套在其他object声明和嵌套类中。

另外还需要注意:

object 定义后即刻实例化

因此 object 不能有定义构造函数

定义在类内部的 object 并不能访问类的成员

二. companion object

和 object 不同, companion object 的定义完全属于类的本身,所以 companion object 肯定是不能脱离类而定义在全局之中。它类似 Java 里的 static 变量,所以我们定义 companion object 变量的时候也一般会使用大写的命名方式。

和 object 类似,可以给 companion object 命名,也可以不给名字,这个时候它会有个默认的名字: Companion ,而且,它只在类里面能定义一次:

class MyClass {
    companion object CompanionName {
        val INNER_PARAMETER = "can only be inner"
        fun newInstance() = MyClass("name")
    }
}
class MyClass1 {
    companion object {
        val INNER_PARAMETER = "can only be inner"
    }
}
fun main(vararg args:String) {
    println(MyClass.CompanionName.INNER_PARAMETER == MyClass1.INNER_PARAMETER)  //print: true
    println(MyClass1.Companion.INNER_PARAMETER == MyClass1.INNER_PARAMETER)     //print: true
}

扩展功能是Kotlin的一个强大的特性,一个类的companion object也可以进行扩展

class MyClass {
    companion object {
        val INNER_PARAM = "hhhhh"
    }
    fun main(varargs args: String) {
        MyClass.Companion.foo() {
            print(this.INNER_PARAM)
        }
        MyClass.foo()
    }
}

虽然companion object中声明的变量类似于Java中的静态变量,但是在运行时他们仍然是一个真正实例的成员变量,所以companion可以实现一个接口

interface Factory {
    fun create(): T
}
class MyClass {
    companion object : Factory {
        override fun create(): MyClass = MyClass()
    }
}

可以使用 @JvmStatic 使Companion object 的成员真正成为静态成员。

ps:

object表达式是在使用的地方立即被初始化和执行的

object声明(一个类)是延迟加载的,只有当第一次被访问时才会初始化,所以被用来实现单例

companion object是当包含它的类被加载时就初始化了的,这一点和Java的static还是一样的

三. 总结

通过上面的一些介绍,对于object 和companion object 的用法有了一定的了解,其实官方并不建议我们到处滥用object 关键字,因为它不利于控制也不易于测试,毕竟它会在声明时就初始化。一般情况下我们使用的使用就是匿名内部类或者单例模式,而companion object一般用来声明静态域。

你可能感兴趣的:(Kotlin中的object和companion object关键字)