有关 Kotlin Companion 我们需要了解到的几个知识点
Kotlin
带给Java
开发者最大的变化就是去掉了 static
关键字。所以 Kotlin
类中没有真正的静态方法或块的定义。如果你必须使用静态方法,可以在class中使用Companion
对象包装静态引用或方法的定义。这种方式看起来和直接定义static
方法区别不大,但其实有本质的不同。
首先,companion
对象是一个真实对象,而不是class,并且是单例模式。实际上,如果在你的class中也定义一个或多个单例实例,并以同样的方式操作它,它们效果和companion
也是一样的。意味着,工作中如果管理静态属性,你不用将它们局限在一个静态class中了。companion
关键字只不过是访问对象的快捷方式,可以直接通过类名访问到该对象的内容(如果在同一个类中使用companion
的属性或方法,也可以完全放弃类名)。下面三种方式的调用,在testCompanion()
中,都是正确的。
如果在Java 中写同样的测试代码,代码稍有不同:
区别是,Companion
作为static 成员暴露在Java 代码中(虽然它的首字母是大写的C,但其实这是一个object 实例),FakeCompanion
指的是我们第二个单例对象的类名。
第二个例子中,我们在Java中,使用INSTANCE
属性名实际访问到实例(我们可以在IntelliJ 或 Android Studio 使用菜单中的“Show Kotlin Bytecode”
,对比相应decompile Java 代码)。
两个例子中(Kotlin 和 Java), 使用companion object
相对使用fake one
语法更短。Kotlin 为了兼容Java的调用,提供了一些annotation
。在annotation
帮助下Java也可以使用相同的方式,和Kotlin 调用方法一样,调用companion
内容。
类似的annotation
包括:@JvmField
, @JvmStatic
等。
@JvmField
,它的作用是通知编译器不用生成getter
和setter
方法,而使用Java 类变量代替。但是副作用是,标记的作用域不会在companion
对象内部,而是成为了Java 的static 作用域。从Kotlin 的角度看,没有什么区别,但是如果看生成的字节码,你就会注意到companion
对象和它的成员都定义在同一级别上,作为类中的static
成员。
另一个有用的 annotation 是 @JvmStatic
。这个 annotation 允许你用静态方法的方式访问companion
对象中的方法。但需要注意的是,这种方式,方法并没有移出到companion
对象之外。编译器只是添加一个额外的 「额外静态方法」在类中,代理了companion
对象的方法。(如果你是使用在properties
上,则会生成getters
和setters
的静态代理方法)。
看一下下面这个Kotlin
的例子:
这是相应的Java
代码(因为完整的代码太长了,所以简化一下)
看起来这些差异并不显著,但是在某些特殊的情况下就会是一个问题。比如使用Dagger
模块,使用Dagger
模块时,可以使用静态方法提高性能,但如果你这么做的话,你的模块中除静态方法之外包含了其他方法会导致编译失败。因为Kotlin
在类中不仅包含了静态方法,也包含了静态的companion
对象。如果想只包含静态方法就不能使用这种方式。
不用这么快就放弃。这并不意味着你不能只包含静态方法,只是操作有点不同:你可以使用 Kotlin 的单例模式(使用 object 关键字替换 class 关键字)然后用 @JvmStatic annotation 标注每个方法。如下例子中,生成的byte code
中不再包含companion
对象,静态方法直接集成在类中。
到此,你应该发现Companion
对象只是一种特殊单例模式。进而可以发现,你不一定需要 Companion 对象来创建 static 方法或字段,甚至连对象都不需要!仅仅思考一下顶层的方法或常量:他们已经是 static 的成员方法或字段了(注意,MyFile.kt
会默认生成一个名为 MyFile
的类)。
我们回到Companion
的主题,我们已经了解到companion
对象就是一个对象,接下来我们谈谈Companion
的继承和多态。
Companion
对象不一定是一个没有类型或父类型的匿名对象。它不仅可以有父类,甚至可以实现接口,有自己的名字。它不一定非要叫做companion
,例子如下:
上面的companion
对象名字是CREATOR
并且它实现了 Android Parcelable.Creator
接口,而且比使用companion
加@JvmField
的方式更加简洁。
还可以更加简洁的实现接口的方式,可以使用代理,例如:
如此一来,就可以同时给几个对象添加静态方法了。需要注意的是,当我们使用代理方式,companion
对象是没有 body 的。
最后,我们的 companion
对象是可以被扩展的。也就意味着,我们可以给一个已经存在的 class 添加静态方法或属性,例子如下:
我们可以利用这种方式方便的添加工厂扩展方法。
总而言之,companion
对象不仅仅是对缺少静态方法的一种补充:
它是真正的
kotlin
对象,包含了名称和类型,还有额外的功能。它并不需要静态成员和方法,有其他的选择,比如使用单例模式和顶级方法。
和大多数东西一样,Kotlin
的学习意味着你的设计需要有一点点转变,但并不像Java
那么严格的限制你的选择。而是提供了一些开放的,新的,更加整洁的特性。
版权声明: 本文为 InfoQ 作者【王泰】的原创文章。
原文链接:【http://xie.infoq.cn/article/e48364447b94d307c111a16a8】。文章转载请联系作者。
评论