Companion object
Companion object

Companion object

提供方: JetBrains 学院

您已经知道如何创建与类关联的单例。然而,在很多情况下,一个类只需要一个单例,而使用类的完整名称可能显得冗长。例如,您可能只需要存储一个公共属性。在这种情况下,您可以使用 Kotlin 的另一个特性:。

伴随物

类内部的可以用 companion 关键字标记:

class Player(val id: Int) {
    companion object Properties {
        /* Default player speed in playing field - 7 cells per turn */
        val defaultSpeed = 7

        fun calcMovePenalty(cell: Int): Int {
            /* calc move speed penalty */
        }
    }
}

/* prints 7 */
println(Player.Properties.defaultSpeed)

伴生对象是附加到单例对象,因此必须访问外部类才能访问它。它让我们能够理解当前对象与外部类之间存在某种关联。例如,我们可以将所有玩家的默认速度存储在 Player 伴生对象中。这也意味着每个 Player 都包含对该伴生对象的引用,并且每次都会返回该伴生对象的实例。

到目前为止,我们一直使用的是带名称的伴随对象。但是,与不同,伴随对象的名称可以省略。让我们来试试:

class Player(val id: Int) {
    companion object {
        /* Default player speed in playing field - 7 cells per turn */
        val defaultSpeed = 7

        fun calcMovePenalty(cell: Int): Int {
            /* calc move speed penalty */
        }
    }
}

/* prints 7 */
println(Player.defaultSpeed)

如您所见,即使省略伴随对象的名称,我们仍然可以通过外部类声明访问它。如果需要以某种方式使用它,我们可以手动定义它。如果伴随对象没有名称,我们也可以使用默认名称 Companion 。

/* prints 7 too */
println(Player.Companion.defaultSpeed)

伴随对象和外部类

伴生对象与外部类紧密关联。您可以在外部类中自由使用伴生对象的属性和函数。例如:

class Deck {
    companion object {
        val size = 10
        val height = 2
        fun volume(bottom: Int, height: Int) = bottom * height
    }

    val square = size * size             //100
    val volume = volume(square, height)  //200
}

但如果外部类有一个与伴生对象同名的属性会发生什么情况呢?在这种情况下,外部类的属性会覆盖伴生对象的属性。

class Deck {
    companion object {
        val size = 10
    }
    val size = 2
    val square = size * size // 4
}

在这种情况下,如果您想使用同伴的属性,则必须使用同伴的名称;如果同伴没有命名,则使用默认名称“同伴”:

class Deck {
    companion object {
        val size = 10
    }
    val size = 2
    val square = Companion.size * Companion.size // 100
}

与嵌套对象的情况类似,你不能在内部类中使用外部类的属性和函数。例如,你不能这样做:

class Deck() {    
    val size = 2
    object Properties {
        val defaultSize = size // you cannot get this variable
    }
}

伴随对象的局限性

每个类只能有一个伴生对象。这意味着你不能为一个类创建多个伴生对象,因为 Kotlin 不支持这种行为,即使它们的名称不同。如果尝试这样做,将会发生错误:

class BadClass {
    companion object Properties {

    }

    companion object Factory {

    }
}

// Compilation error
// Error: Only one companion object is allowed per class

但是,我们可以创建一个伴随对象和多个嵌套对象:

class Player(val id: Int) {
    companion object Properties {
        /* Default player speed in playing field - 7 cells per turn */
        val defaultSpeed = 7

        fun calcMovePenalty(cell: Int): Int {
            /* calc move speed penalty */
        }
    }

    /* creates a new instance of Player */
    object Factory {
        fun create(playerId: Int): Player {
            return Player(playerId)
        }
    }
}

/* prints 7 */
println(Player.Properties.defaultSpeed)

/* also prints 7 */
println(Player.defaultSpeed)

/* prints 13 */
println(Player.Factory.create(13).id)

还有一个限制:我们不能在另一个单例(或伴生对象)中创建伴生对象,因为这违反了全局访问原则。

object OuterSingleton {
    companion object InnerSingleton { // Modifier 'companion' is not applicable inside 'object'  

    }
}

其他语言中的类似物

如果你之前接触过其他编程语言,可能会对伴生对象感到有些困惑。与它最接近的概念是 static 成员。 static 表示带有此字段和方法对于类的所有对象都是通用的,无需创建类的实例即可使用。你可能会在多种编程语言中遇到这个关键字。

例如,在 Java 中, static 的用法如下所示:

class Dog {
    public static int numOfPaws = 4;

    public static String createSound() {
        return "WUF-WUF";
    }
}
/*prints WUF-WUF*/
System.out.println(Dog.createSound());

如您所知,Kotlin 没有 static 关键字。如果您需要类的所有实例都具有某些共同属性,可以使用伴生对象:

class Dog {
    companion object {
        val numOfPaws: Int = 4
        fun createSound(): String = "WUF-WUF"
    }
}
/*prints WUF-WUF*/
println(Dog.createSound())

如您所见,使用伴生对象时,您无需创建类实例即可使用此函数!请记住,伴生对象与 Java 的 static 初始化器不同。在 Kotlin 中,伴生对象是一个嵌套对象,它封装了整个类通用的所有方法和字段。

结论

让我们回顾一下!声明伴生对象是组织数据的一种好方法。它类似于嵌套对象,但与类的关联更紧密。你可以像操作伴生对象自身的属性一样,从外部类自由地操作它的属性。当你需要一个与类关联的单例时,可以使用伴生对象:使用伴生对象比使用嵌套类更可取。现在,让我们开始练习吧!

224 名学习者喜欢这篇理论文章, 8 名学习者不喜欢。 你呢?

报告拼写错误

相关条目

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注