有关Kotlin Companion 我们需要了解到的几个知识点
带给 开发者最大的变化就是去掉了 关键字。所以 类中没有真正的静态方法或块的定义。如果你必须使用静态方法,可以在class中使用 对象包装静态引用或方法的定义。这种方式看起来和直接定义 方法区别不大,但其实有本质的不同。
首先, 对象是一个真实对象,而不是class,并且是单例模式。实际上,如果在你的class中也定义一个或多个单例实例,并以同样的方式操作它,它们效果和也是一样的。意味着,工作中如果管理静态属性,你不用将它们局限在一个静态class中了。 关键字只不过是访问对象的快捷方式,可以直接通过类名访问到该对象的内容(如果在同一个类中使用 的属性或方法,也可以完全放弃类名)。下面三种方式的调用,在中,都是正确的。
class TopLevelClass {
companion object {
fun doSomeStuff(){
...
}
}
object FakeCompanion {
fun doOtherStuff() {
...
}
}
}
fun testCompanion() {
TopLevelClass.doSomeStuff()
TopLevelClass.Companion.doSomeStuff()
TopLevelClass.FakeCompanion.doOtherStuff()
}
如果在Java 中写同样的测试代码,代码稍有不同:
public void testCompanion() {
TopLevelClass.Companion.doSomeStuff();
TopLevelClass.FakeCompanion.INSTANCE.doOtherStuff();
}
区别是,作为static 成员暴露在Java 代码中(虽然它的首字母是大写的C,但其实这是一个object 实例),指的是我们第二个单例对象的类名。
第二个例子中,我们在Java中,使用 属性名实际访问到实例(我们可以在IntelliJ 或 Android Studio 使用菜单中的,对比相应decompile Java 代码)。
两个例子中(Kotlin 和 Java), 使用 相对使用 语法更短。Kotlin 为了兼容Java的调用,提供了一些 。在 帮助下Java也可以使用相同的方式,和Kotlin 调用方法一样,调用内容。
类似的 包括:, 等。
,它的作用是通知编译器不用生成和方法,而使用Java 类变量代替。但是副作用是,标记的作用域不会在对象内部,而是成为了Java 的static 作用域。从Kotlin 的角度看,没有什么区别,但是如果看生成的字节码,你就会注意到对象和它的成员都定义在同一级别上,作为类中的成员。
另一个有用的 annotation 是 。这个 annotation 允许你用静态方法的方式访问 对象中的方法。但需要注意的是,这种方式,方法并没有移出到对象之外。编译器只是添加一个额外的 「额外静态方法」在类中,代理了对象的方法。(如果你是使用在上,则会生成和的静态代理方法)。
看一下下面这个的例子:
class MyClass {
companion object {
@JvmStatic
fun aStaticFuncation (){}
}
}
这是相应的代码(因为完整的代码太长了,所以简化一下)
public class MyClass {
public static final MyClass.Companion Companion = new MyClass.Companion();
fun aStaticFunction() {
Companion.aStaticFunction();
}
public static final class Companion {
public final void aStaticFunction() {}
}
}
看起来这些差异并不显著,但是在某些特殊的情况下就会是一个问题。比如使用模块,使用 模块时,可以使用静态方法提高性能,但如果你这么做的话,你的模块中除静态方法之外包含了其他方法会导致编译失败。因为在类中不仅包含了静态方法,也包含了静态的对象。如果想只包含静态方法就不能使用这种方式。
不用这么快就放弃。这并不意味着你不能只包含静态方法,只是操作有点不同:你可以使用 Kotlin 的单例模式(使用 object 关键字替换 class 关键字)然后用 @JvmStatic annotation 标注每个方法。如下例子中,生成的中不再包含对象,静态方法直接集成在类中。
@Module
object MyModule {
@Provides
@Singleton
@JvmStatic
fun provideSomething(anObject: MyObject): MyInterface {
return myObject
}
}
到此,你应该发现 对象只是一种特殊单例模式。进而可以发现,你不一定需要 Companion 对象来创建 static 方法或字段,甚至连对象都不需要!仅仅思考一下顶层的方法或常量:他们已经是 static 的成员方法或字段了(注意, 会默认生成一个名为 的类)。
我们回到 的主题,我们已经了解到 对象就是一个对象,接下来我们谈谈的继承和多态。
对象不一定是一个没有类型或父类型的匿名对象。它不仅可以有父类,甚至可以实现接口,有自己的名字。它不一定非要叫做,例子如下:
class ParcelableClass() : Parcelable {
constructor(parcel: Parcel): this()
override fun writeToParcel(parcel: Parcel, flags: Int) {}
override fun describeContents() = 0
companion object CREATOR : Parcelable.Creator {
override fun crateFromParcel(parcel: Parcel): ParcelableClass = ParcelableClass(parcel)
override fun newArray(size: Int): Array = arrayOfNulls(size)
}
}
上面的对象名字是并且它实现了 Android 接口,而且比使用加的方式更加简洁。
还可以更加简洁的实现接口的方式,可以使用代理,例如:
class MyObject {
companion object : Runnable by MyRunnable()
}
如此一来,就可以同时给几个对象添加静态方法了。需要注意的是,当我们使用代理方式,对象是没有 body 的。
最后,我们的 对象是可以被扩展的。也就意味着,我们可以给一个已经存在的 class 添加静态方法或属性,例子如下:
class MyObject {
companion object {}
fun useCompanionExtension() {
someExtension()
}
}
fun MyObject.Companion.someExtension() {}
我们可以利用这种方式方便的添加工厂扩展方法。
总而言之, 对象不仅仅是对缺少静态方法的一种补充:
它是真正的 对象,包含了名称和类型,还有额外的功能。
它并不需要静态成员和方法,有其他的选择,比如使用单例模式和顶级方法。
和大多数东西一样,的学习意味着你的设计需要有一点点转变,但并不像那么严格的限制你的选择。而是提供了一些开放的,新的,更加整洁的特性。