写点什么

有关 Kotlin Companion 我们需要了解到的几个知识点

用户头像
王泰
关注
发布于: 2020 年 04 月 25 日
有关Kotlin Companion 我们需要了解到的几个知识点

Kotlin 带给Java 开发者最大的变化就是去掉了 static 关键字。所以 Kotlin 类中没有真正的静态方法或块的定义。如果你必须使用静态方法,可以在class中使用Companion 对象包装静态引用或方法的定义。这种方式看起来和直接定义static 方法区别不大,但其实有本质的不同。



首先,companion 对象是一个真实对象,而不是class,并且是单例模式。实际上,如果在你的class中也定义一个或多个单例实例,并以同样的方式操作它,它们效果和companion也是一样的。意味着,工作中如果管理静态属性,你不用将它们局限在一个静态class中了。companion 关键字只不过是访问对象的快捷方式,可以直接通过类名访问到该对象的内容(如果在同一个类中使用companion 的属性或方法,也可以完全放弃类名)。下面三种方式的调用,在testCompanion()中,都是正确的。

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();
}



区别是,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,它的作用是通知编译器不用生成gettersetter方法,而使用Java 类变量代替。但是副作用是,标记的作用域不会在companion对象内部,而是成为了Java 的static 作用域。从Kotlin 的角度看,没有什么区别,但是如果看生成的字节码,你就会注意到companion对象和它的成员都定义在同一级别上,作为类中的static成员。



另一个有用的 annotation 是 @JvmStatic。这个 annotation 允许你用静态方法的方式访问companion 对象中的方法。但需要注意的是,这种方式,方法并没有移出到companion对象之外。编译器只是添加一个额外的 「额外静态方法」在类中,代理了companion对象的方法。(如果你是使用在properties上,则会生成getterssetters的静态代理方法)。



看一下下面这个Kotlin的例子:



class MyClass {
companion object {
@JvmStatic
fun aStaticFuncation (){}
}
}



这是相应的Java代码(因为完整的代码太长了,所以简化一下)



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() {}
}
}



看起来这些差异并不显著,但是在某些特殊的情况下就会是一个问题。比如使用Dagger模块,使用Dagger 模块时,可以使用静态方法提高性能,但如果你这么做的话,你的模块中除静态方法之外包含了其他方法会导致编译失败。因为Kotlin在类中不仅包含了静态方法,也包含了静态的companion对象。如果想只包含静态方法就不能使用这种方式。



不用这么快就放弃。这并不意味着你不能只包含静态方法,只是操作有点不同:你可以使用 Kotlin 的单例模式(使用 object 关键字替换 class 关键字)然后用 @JvmStatic annotation 标注每个方法。如下例子中,生成的byte code中不再包含companion对象,静态方法直接集成在类中。

@Module
object MyModule {
@Provides
@Singleton
@JvmStatic
fun provideSomething(anObject: MyObject): MyInterface {
return myObject
}
}



到此,你应该发现Companion 对象只是一种特殊单例模式。进而可以发现,你不一定需要 Companion 对象来创建 static 方法或字段,甚至连对象都不需要!仅仅思考一下顶层的方法或常量:他们已经是 static 的成员方法或字段了(注意,MyFile.kt 会默认生成一个名为 MyFile 的类)。



我们回到Companion 的主题,我们已经了解到companion 对象就是一个对象,接下来我们谈谈Companion继承多态



Companion 对象不一定是一个没有类型或父类型的匿名对象。它不仅可以有父类,甚至可以实现接口,有自己的名字。它不一定非要叫做companion,例子如下:



class ParcelableClass() : Parcelable {
constructor(parcel: Parcel): this()
override fun writeToParcel(parcel: Parcel, flags: Int) {}
override fun describeContents() = 0
companion object CREATOR : Parcelable.Creator<ParcelableClass> {
override fun crateFromParcel(parcel: Parcel): ParcelableClass = ParcelableClass(parcel)
override fun newArray(size: Int): Array<ParcelableClass?> = arrayOfNulls(size)
}
}



上面的companion对象名字是CREATOR并且它实现了 Android Parcelable.Creator接口,而且比使用companion@JvmField的方式更加简洁。



还可以更加简洁的实现接口的方式,可以使用代理,例如:



class MyObject {
companion object : Runnable by MyRunnable()
}



如此一来,就可以同时给几个对象添加静态方法了。需要注意的是,当我们使用代理方式,companion对象是没有 body 的。



最后,我们的 companion 对象是可以被扩展的。也就意味着,我们可以给一个已经存在的 class 添加静态方法或属性,例子如下:

class MyObject {
companion object {}
fun useCompanionExtension() {
someExtension()
}
}
fun MyObject.Companion.someExtension() {}



我们可以利用这种方式方便的添加工厂扩展方法。



总而言之,companion 对象不仅仅是对缺少静态方法的一种补充:



  • 它是真正的kotlin 对象,包含了名称和类型,还有额外的功能。

  • 它并不需要静态成员和方法,有其他的选择,比如使用单例模式和顶级方法。

和大多数东西一样,Kotlin的学习意味着你的设计需要有一点点转变,但并不像Java那么严格的限制你的选择。而是提供了一些开放的,新的,更加整洁的特性。



发布于: 2020 年 04 月 25 日阅读数: 89
用户头像

王泰

关注

还未添加个人签名 2011.07.21 加入

极客神灯创始人

评论

发布
暂无评论
有关Kotlin Companion 我们需要了解到的几个知识点