上一节中说到了对象声明,这次我们聊聊对象声明的一种特殊情况,对象声明在一个类内部,也就是标题所说的伴生对象。
伴生对象最简单直白的理解就是与一个类相伴而生的对象,由于它处于类内部,所以必然和包含它的类存在某种联系。
基本用法
伴生对象使用关键字companion来声明,看起来就像是在object关键字前面加的一个修饰符。它的一般格式如下:
class 外部类名{
companion object 伴生对象名{
//属性
//方法
}
}
简单的代码示例如下:
class A{
companion object {
fun test(){
println("Companion Object...")
}
}
}
你会发现上面的代码中没有伴生对象名,因为它是可以省略的。下面就依次看看伴生对象都用在那些地方。
弥补static关键字缺憾
在Java中有static关键字表示静态成属性和方法,但在Kotlin中没有static关键字,所以伴生对象和顶层函数一起来弥补了这一缺憾,还是看上面给出的第一个例子,那我们怎么去调用伴生对象中的方法呢。
上面的代码连伴生对象的名字都省略了,所以我们只能通过外部类来调用。格式如下:
外部类.伴生对象内部方法
如下所示:
fun main(args: Array<String>) {
A.test()
}
当然对于指定了伴生对象名的情况,调用格式如下:
外部类.伴生对象名.伴生对象内部方法
代码示例如下:
class B{
companion object KB{
fun testB(){
println("Companion Object...")
}
}
}
fun main(args: Array<String>) {
B.testB()
B.KB.testB()
}
当然你会发现,你在调用时仍然可以省略掉伴生对象名,就像Java中通过类名直接调用静态方法一样。
当然,对于在声明时省略了伴生对象名的情况,Kotlin也提供了一个叫Companion的默认名称,所以我们也可以像下面的方式进行调用:
class A{
companion object {
fun test(){
println("Companion Object...")
}
}
}
fun main(args: Array<String>) {
A.test()
//默认名称
A.Companion.test()
}
其实伴生对象的出现也是为了弥补顶层函数的不足,顶层函数不能访问一个类中的private成员,而伴生对象是可以访问的,如下面代码所示:
class C{
private val age: Int = 0;
companion object {
fun out(){
//伴生对象中可以调用外部的私有成员
println(C().age)
}
}
}
fun main(args: Array<String>) {
//顶层函数不能调用外部类的私有成员
println(C().age) //报错
}
实现接口
伴生对象在声明的时候可以像其他类那样实现接口,并实现接口中的方法,如下面的代码所示:
class People(val name: String){
//伴生对象实现接口
companion object : Log{
//实现接口的方法
override fun printLog(msg: String) {
println("msg ------- $msg")
}
}
}
//该方法的参数是接口实例
fun callLog(log: Log){
log.printLog("msg")
}
fun main(args: Array<String>) {
//传入实现接口的伴生对象的类
callLog(People)
}
上面的代码可以这样理解,伴生对象实现了接口,伴生对象又与外部类存在关联,所以相当于外部类也实现了接口,所以我们可直接传入外部类的名称,这里的这个名称就指代了那个伴生对象。
伴生对象扩展
在前面的内容中我们也说过方法扩展,那这里我们的伴生对象也可以进行扩展,我们可以在其上扩展我们自己的方法。这里所谓的扩展就是在声明伴生对象的时候没有指定函数,而是在类的外部来给伴生对象增加方法,来看看下面的代码示例:
class Person(val name: String){
companion object {
//伴生对象内部没有方法
}
}
//伴生对象扩展方法
fun Person.Companion.toJson(person: Person): String{
//具体实现
return "";
}
fun main(args: Array<String>) {
//调用方法
val str = Person.toJson(Person("小白"))
}
上面的扩展方法和函数的扩展差别不大,主要是要访问到扩展对象再在其上扩展方法。
写在最后
这一节的伴生对象,总觉得还是有点绕,而且使用场景比较多,容易出错,后续待理解更加深入了会再写文章介绍。