Kotlin中关于CompanionObject的那些事
翻译说明:
原标题: A few facts about Companion objects
原⽂作者:
Kotlin给Java开发者带来最⼤改变之⼀就是废弃了static修饰符。与Java不同的是在Kotlin的类中不允许你声明静态成员或⽅法。相反,你必须向类中添加Companion对象来包装这些静态引⽤: 差异看起来似乎很⼩,但是它有⼀些明显的不同。
⾸先,companion伴⽣对象是个实际对象的单例实例。你实际上可以在你的类中声明⼀个单例,并且可以像companion伴⽣对象那样去使⽤它。这就意味着在实际开发中,你不仅仅只能使⽤⼀个静态对象来管理你所有的静态属性!companion这个关键字实际上只是⼀个快捷⽅式,允许你通过类名访问该对象的内容(如果伴⽣对象存在⼀个特定的类中,并且只是⽤到其中的⽅法或属性名称,那么伴⽣对象的类名可以省略不写)。就编译⽽⾔,下⾯的testCompanion()⽅法中的三⾏都是有效的语句。
class TopLevelClass {
companion object {
fun doSomeStuff() {
...
}
}
object FakeCompanion {
fun doOtherStuff() {
...
}
}
}
fun testCompanion() {
TopLevelClass.doSomeStuff()
TopLevelClass.Companion.doSomeStuff()
TopLevelClass.FakeCompanion.doOtherStuff()
}
为了兼容的公平性,companion关键字还提供了更多选项,尤其是与Java互操作性相关选项。果您尝试在Java类中编写相同的测试代码,调⽤⽅式可能会略有不同:
public void testCompanion() {
TopLevelClass.Companion.doSomeStuff();
TopLevelClass.FakeCompanion.INSTANCE.doOtherStuff();
}
区别在于: Companion作为Java代码中静态成员开放(实际上它是⼀个对象实例,但是由于它的名称是以⼤写的C开头,所以有点存在误导性),⽽FakeCompanion引⽤了我们的第⼆个单例对象的类名。在第⼆
个⽅法调⽤中,我们需要使⽤它的INSTANCE属性来实际访问Java 中的实例(你可以打开IntelliJ IDEA或AndroidStudio中的"Show Kotlin Bytecode"菜单栏,并点击⾥⾯"Decompile"按钮来查看反编译后对应的Java代码)
在这两种情况下(不管是Kotlin还是Java),使⽤伴⽣对象Companion类⽐FakeCompanion类那种调⽤语法更加简短。此外,由于Kotlin提供⼀些注解,可以让编译器⽣成⼀些简短的调⽤⽅式,以便于在Java代码中依然可以像在Kotlin中那样简短形式调⽤。
@JvmField注解,例如告诉编译器不要⽣成getter和setter,⽽是⽣成Java中成员。在伴⽣对象的作⽤域内使⽤该注解标记某个成员,它产⽣的副作⽤是标记这个成员不在伴⽣对象内部作⽤域,⽽是作为⼀个Java最外层类的静态成员存在。从Kotlin的⾓度来看,这没有什么太⼤区别,但是如果你看⼀下反编译的字节代码,你就会注意到伴⽣对象以及他的成员都声明和最外层类的静态成员处于同⼀级别。
另⼀个有⽤的注解 @JvmStatic.这个注解允许你调⽤伴⽣对象中声明的⽅法就像是调⽤外层的类的静态⽅法⼀样。但是需要注意的是:在这种情况下,⽅法不会和上⾯的成员⼀样移出伴⽣对象的内部作⽤域。因为编译器只是向外层类中添加⼀个额外的静态⽅法,然后在该⽅法内部⼜委托给伴⽣对象。
⼀起来看⼀下这个简单的Kotlin类例⼦:
class MyClass {
companion object {
@JvmStatic
fun aStaticFunction() {}
}
}
这是相应编译后的Java简化版代码:
public class MyClass {
public static final MyClass.Companion Companion = new MyClass.Companion();
fun aStaticFunction() {//外层类中添加⼀个额外的静态⽅法
Companion.aStaticFunction();//⽅法内部⼜委托给伴⽣对象的aStaticFunction⽅法
}
public static final class Companion {
public final void aStaticFunction() {}
}
}
这⾥存在⼀个⾮常细微的差别,但在某些特殊的情况下可能会出问题。例如,考虑⼀下Dagger中的module(模块)。当定义⼀个Dagger模块时,你可以使⽤静态⽅法去提升性能,但是如果你选择这样做,如果您的模块包含静态⽅法以外的任何内容,则编译将失败。由于Kotlin 在类中既包含静态⽅法,也保留了静态伴⽣对象,因此⽆法以这种⽅式编写仅仅包含静态⽅法的Kotlin类。
但是不要那么快放弃! 这并不意味着你不能这样做,只是它需要⼀个稍微不同的处理⽅式:在这种特殊的情况下,你可以使⽤Kotlin单例(使⽤object对象表达式⽽不是class类)替换含有静态⽅法的Java类并在每个⽅法上使⽤@JvmStatic注解。如下例所⽰:在这种情况下,⽣成的字节代码不再显⽰任何伴⽣对象,静态⽅法会附加到类中。
@Module
object MyModule {
@Provides
@Singleton
@JvmStatic
fun provideSomething(anObject: MyObject): MyInterface {
return myObject
}
}
这⼜让你再⼀次明⽩了伴⽣对象仅仅是单例对象的⼀个特例。但它⾄少表明与许多⼈的认知是相反的,你不⼀定需要⼀个伴⽣对象来维护静态⽅法或静态变量。你甚⾄根本不需要⼀个对象来维护,只要考虑顶层函数或常量:它们将作为静态成员被包含在⼀个⾃动⽣成的类中(默认情况下,例如MyFileKt会作为MyFile.kt⽂件⽣成的类名,⼀般⽣成类名以Kt为后缀结尾)
我们有点偏离这篇⽂章的主题了,所以让我们继续回到伴⽣对象上来。现在你已经了解了伴⽣对象实质就是对象,也应该意识到它开放了更多的可能性,例如继承和多态。
这意味着你的伴⽣对象并不是没有类型或⽗类的匿名对象。它不仅可以拥有⽗类,⽽且它甚⾄可以实现接⼝以及含有对象名。它不需要被称为companion。这就是为什么你可以这样写⼀个Parcelable类:
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 createFromParcel(parcel: Parcel): ParcelableClass = ParcelableClass(parcel)
override fun newArray(size: Int): Array<ParcelableClass?> = arrayOfNulls(size)
}
}
这⾥, 伴⽣对象名为CREATOR,它实现了Android中的Parcelable.Creator接⼝,允许遵守Parcelable约定,同时保持⽐使⽤@JvmField 注释在伴随对象内添加Creator对象更直观。Kotlin中引⼊了@Parcelize注解,以便于可以获得所有样板代码,但是在这不是重点…
为了使它变得更简洁,如果你的伴⽣对象可以实现接⼝,它甚⾄可以使⽤Kotlin中的代理来执⾏此操作:
class MyObject {
companion object : Runnable by MyRunnable()
}
这将允许您同时向多个对象中添加静态⽅法!请注意,伴⽣对象在这种情况下甚⾄不需要作⽤域体,因为它是由代理提供的。
最后但同样重要的是,你可以为伴⽣对象定义扩展函数! 这就意味着你可以在现有的类中添加静态⽅法或静态属性,如下例所⽰:
class MyObject {
companion object
fun useCompanionExtension() {
someExtension()
}
}
fun MyObject.Companion.someExtension() {}//定义扩展函数
这样做有什么意义?我真的不知道。虽然Marcin Moskala建议使⽤此操作将静态⼯⼚⽅法以Companion的扩展函数的形式添加到类中。
kotlin修饰符
总⽽⾔之,伴⽣对象不仅仅是为了给缺少static修饰符的使⽤场景提供解决⽅案:
它们是真正的Kotlin对象,包括名称和类型,以及⼀些额外的功能。
他们甚⾄可以不⽤于仅仅为了提供静态成员或⽅法场景。可以有更多其他选择,⽐如他们可以⽤作单例对象或替代顶层函数的功能。
与⼤多数场景⼀样,Kotlin意味着在你设计过程需要有⼀点点转变,但与Java相⽐,它并没有真正限制你的选择。如果有的话,也会通过提供⼀些新的、更简洁的⽅式让你去使⽤它。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。