标签归档:companion object

11. Kotlin 类声明与伴生对象(companion)

Published / by sickworm / Leave a Comment

1. companion object 的诞生

Scala 说,要有伴生对象。

于是 Kotlin 便有了 companion object。

companion object 的出现是为了解决 Java static 方法的反面向对象(Anti-OOP)的问题。static 方法无法声明为接口,无法被重写——用更学术的话来说,static 方法没有面向对象的消息传递延迟绑定特性[参考]。而 Scala 为了完成一切皆对象的使命,以及提高与 Java 的兼容性,提出了伴生对象这个概念来代替 static 方法。随后出身的 Kotlin 也借鉴了这个概念。

companion 伴生对象是一个对象,它在类初始化时被实例化。 因为伴生对象不再是类的 static 方法,而是某个类的实例化对象,所以它可以声明接口,里面的方法也可以被重写,具备面向对象的所有特点。

2. companion 的实现

在 Kotlin 中,调用 Java 的 static 方法和调用 Kotlin 的 companion object 方法是一样的:

AJavaClass.staticFun()
AKotlinClass.companionFun()

而在 Java 中,调用 static 方法和 Kotlin 的伴生 companion object 方法,有一点不同:

AJavaClass.staticFun();
AKotlinClass.Companion.companionFun();

从 Java 的调用我们可以发现,companion object 的 JVM 字节码体现,是一个声明了一个叫 Companion 的 static 变量。

而 Kotlin 调用一致,其实是编译器的特殊处理的结果。

如果我们反编译AKotlinClass,可以看到:

// AKotlinClass.class
public final class AKotlinClass {
    public static final Companion Companion = new Companion(null);
}
// AKotlinClass$Companion.class
public final class AKotlinClass$Companion {
    private DownloadExecutor$Companion() {
    }

    public /* synthetic */ DownloadExecutor$Companion(DefaultConstructorMarker $constructor_marker) {
        this();
    }

    public final void companionFun() {
    }
}

可以看到,Companion 是一个叫 AKotlinClass$Companion 的类的实例,带 $ 符号表示这个类是 AKotlinClass 的内部类,名字叫 Companion,所以在AKotlinClass中直接new Companion(null)即可。

你也许还留意到实例化 Companion 使用的是一个带 DefaultConstructorMarker 入参的构造器。它出现的场景是,如果是 Kotlin 编译器生成的特殊构造器,就会带这个参数。在这里,Kotlin 希望能够实例化 Companion 类,但又不想声明一个 public 的构造器,于是就声明了这样一个特殊的构造器。DefaultConstructorMarker 值永远为 null。

DefaultConstructorMarker 出现的另一个场景是:带默认参数的构造器。因为带了默认参数后,构造器需要增加 int 类型的 bitmask 入参,来识别哪个入参需要被替换为默认参数。而为构造器增加一个入参,容易和其他构造器“撞车”,即构造器的入参完全一样,导致编译失败。所以默认参数的构造器末尾还会增加一个 DefaultConstructorMarker 入参,来防止构造器参数一致的问题。[参考]

版权所有,转载请注明出处:
https://sickworm.com/?p=1816