177

“伴侣对象”的本意是什么?到目前为止,我一直在使用它来static在需要时替换 Java。

我很困惑:

  • 为什么叫“伴侣”?
  • 这是否意味着要创建多个静态属性,我必须在块内将它们组合在一起companion object
  • 为了立即创建一个作用域为类的单例实例,我经常写

companion object {
    val singleton by lazy { ... }
}

这似乎是一种单一的方式。有什么更好的方法?

4

5 回答 5

126
  • “伴侣对象”的本意是什么?为什么叫“伴侣”?

    首先,Kotlin 没有使用 Java 的static成员概念,因为 Kotlin 有自己的s概念object来描述与单例状态相关的属性和函数,并且static类的 Java 部分可以优雅地表示为单例:它是一个单例对象可以通过类名来调用。因此命名:它是一个类附带的对象。

    它的名字以前是class objectanddefault object,后来改名为companion objectwhich 更清晰,也与Scala 伴生对象一致。

    除了命名之外,它比 Javastatic成员更强大:它可以扩展类和接口,并且您可以像其他对象一样引用和传递它。

  • 这是否意味着要创建多个静态属性,我必须在companion object块内将它们组合在一起?

    是的,这是惯用的方式。或者您甚至可以按照它们的含义将它们分组到非伴随对象中:

    class MyClass {
        object IO {
            fun makeSomethingWithIO() { /* ... */ }
        }
    
        object Factory {
            fun createSomething() { /* ... */ }
        }
    }
    
  • 为了立即创建一个作用域为类的单例实例,我经常编写/*...*/这似乎是一种单一的做法。有什么更好的方法?

    这取决于您在每种特定情况下需要什么。您的代码非常适合存储绑定到在第一次调用它时初始化的类的状态。

    如果您不需要它与类连接,只需使用对象声明:

    object Foo {
        val something by lazy { ... }
    }
    

    您还可以删除lazy { ... }委托以使属性在第一类的使用情况下初始化,就像 Java 静态初始化器一样

    您可能还会发现初始化单例状态的有用方法。

于 2016-07-14T19:12:42.440 回答
19

为什么叫“伴侣”?

该对象是实例的伴侣。IIRC 在这里进行了长时间的讨论:即将到来的更改类对象重新思考

这是否意味着要创建多个静态属性,我必须将其组合在伴生对象块中?

是的。每个“静态”属性/方法都需要放在这个伴侣中。

为了立即创建一个作用域为类的单例实例,我经常写

您不会立即创建单例实例。它是在singleton第一次访问时创建的。

这似乎是一种单一的方式。有什么更好的方法?

而是去object Singleton { }定义一个单例类。请参阅:对象声明 您不必创建 的实例Singleton,只需像这样使用它Singleton.doWork()

请记住,Kotlin 提供了其他东西来组织您的代码。现在有简单静态函数的替代方法,例如您可以使用顶级函数。

于 2016-07-14T19:00:55.073 回答
8

为什么叫“伴侣”?

类中的对象声明可以用伴随关键字标记:

class MyClass {
    companion object Factory {
        fun create(): MyClass = MyClass()
    }
}

可以通过简单地使用类名作为限定符来调用伴生对象的成员:

val instance = MyClass.create()

如果你只使用'object'而不使用'companion',你必须这样做:

val instance = MyClass.Factory.create()

在我的理解中,'companion' 意味着这个对象与外部类是相伴的。

于 2018-01-17T01:33:51.337 回答
8

当具有相关功能的类/对象属于一起时,它们就像彼此的伴侣。在这种情况下,同伴是指合伙人或合伙人。


陪伴的理由

更干净的顶级命名空间

当某个独立函数打算仅与某个特定类一起使用时,我们不是将其定义为顶级函数,而是在该特定类中定义它。这可以防止顶级命名空间的污染,并有助于 IDE 提供更多相关的自动完成提示。

包装方便

当类/对象在它们提供给彼此的功能方面彼此密切相关时,将它们保持在一起很方便。我们省去了将它们保存在不同文件中并跟踪它们之间关联的工作。

代码可读性

仅通过查看伙伴关系,您就会知道这object为外部类提供了帮助器功能,并且可能不会在任何其他上下文中使用。因为如果要与其他类一起使用,它将是一个单独的顶级classobject或函数。


主要目的companion object

问题:同伴class

让我们看看伴随对象解决的问题种类。我们将举一个简单的现实世界示例。假设我们有一个类User来代表我们的应用程序中的用户:

data class User(val id: String, val name: String)

interface为数据访问对象UserDao添加或User从数据库中删除:

interface UserDao {
    fun add(user: User)
    fun remove(id: String)
}

现在,由于 的功能User和实现在UserDao逻辑上彼此相关,我们可以决定将它们组合在一起:

data class User(val id: String, val name: String) {
    class UserAccess : UserDao {
        override fun add(user: User) { }
        override fun remove(id: String) { }
    }
}

用法:

fun main() {
    val john = User("34", "John")
    val userAccess = User.UserAccess()
    userAccess.add(john)
}

虽然这是一个很好的设置,但其中存在几个问题:

  1. UserAccess添加/删除User.
  2. UserAccess可以创建我们不想要的多个实例。我们只需要在整个应用程序中进行一次数据访问object(单例) 。User
  3. 该类有可能UserAccess与其他类一起使用或扩展。所以,它并没有让我们的意图明确我们想要做什么。
  4. 命名userAccess.add()oruserAccess.addUser()似乎不是很优雅。我们更喜欢类似User.add().

解决方案:companion object

User课堂上,我们只需将这两个词替换为另外class UserAccess两个词companion object,就完成了!上面提到的所有问题都突然解决了:

data class User(val id: String, val name: String) {
    companion object : UserDao {
        override fun add(user: User) { }
        override fun remove(id: String) { }
    }
}

用法:

fun main() {
    val john = User("34", "John")
    User.add(john)
}

扩展接口和类的能力是将伴随对象与 Java 的静态功能区分开来的特性之一。此外,同伴是对象,我们可以将它们传递给函数并将它们分配给变量,就像 Kotlin 中的所有其他对象一样。我们可以将它们传递给接受这些接口和类并利用多态性的函数。


companion object编译时const

当编译时常量与类密切相关时,可以在companion object.

data class User(val id: String, val name: String) {
    companion object {
        const val DEFAULT_NAME = "Guest"
        const val MIN_AGE = 16
    }
}

这是您在问题中提到的那种分组。这样我们就可以防止顶级命名空间被不相关的常量污染。


companion objectlazy { }

lazy { }构造不是获得单例所必需的。Acompanion object默认是单例的,object它只初始化一次并且是线程安全的。它在加载相应的类时被初始化。lazy { }当您想推迟成员的初始化时使用,或者companion object当您有多个成员希望仅在第一次使用时被初始化时,一个一个地使用:

data class User(val id: Long, val name: String) {
    companion object {

        val list by lazy {
            print("Fetching user list...")
            listOf("John", "Jane")
        }

        val settings by lazy {
            print("Fetching settings...")
            mapOf("Dark Theme" to "On", "Auto Backup" to "On")
        }
    }
}

在这段代码中,获取listsettings是昂贵的操作。因此,我们lazy { }仅在实际需要并首次调用它们时才使用构造来初始化它们,而不是一次全部初始化。

用法:

fun main() {
    println(User.list)      // Fetching user list...[John, Jane]
    println(User.list)      // [John, Jane]
    println(User.settings)  // Fetching settings...{Dark Theme=On, Auto Backup=On}
    println(User.settings)  // {Dark Theme=On, Auto Backup=On}
}

获取语​​句将仅在第一次使用时执行。


companion object用于工厂功能

伴随对象用于定义工厂函数,同时保留constructor private. 例如,newInstance()以下代码片段中的工厂函数通过自动生成用户来创建用户id

class User private constructor(val id: Long, val name: String) {
    companion object {
        private var currentId = 0L;
        fun newInstance(name: String) = User(currentId++, name)
    }
}

用法:

val john = User.newInstance("John")

请注意 是如何constructor保留private的,但companion object可以访问constructor。当您想要提供多种方法来创建对象构造过程复杂的对象时,这很有用。

在上面的代码中,保证了下一代的一致性,id因为 acompanion object是一个单例,只有一个对象会跟踪 ,id不会有任何重复id的 s。

另请注意,伴随对象可以具有currentId表示状态的属性(在这种情况下)。


companion object延期

伴侣对象不能被继承,但我们可以使用扩展函数来增强它们的功能:

fun User.Companion.isLoggedIn(id: String): Boolean { }

的默认类名companion objectCompanion,如果您不指定它。

用法:

if (User.isLoggedIn("34")) { allowContent() }

这对于扩展第三方库类的伴随对象的功能很有用。另一个优于 Javastatic成员的优势。


何时避免companion object

有点相关的成员

当函数/属性不密切相关但仅与某个类有一定程度的相关时,建议您使用顶级函数/属性而不是companion object. 并且最好在类声明之前在与类相同的文件中定义这些函数:

fun getAllUsers() { }

fun getProfileFor(userId: String) { }

data class User(val id: String, val name: String)

坚持单一职责原则

当功能object复杂或类很大时,您可能希望将它们分成单独的类。例如,您可能需要一个单独的类来表示一个User和另一个UserDao用于数据库操作的类。与登录相关的功能的单独UserCredentials类。当您有大量在不同地方使用的常量列表时,您可能希望将它们分组到另一个单独的类或文件UserConstants中。UserSettings代表设置的不同类。另一个类UserFactory来创建不同的实例User等等。


就是这样!希望这有助于使您的代码更符合 Kotlin 的习惯。

于 2021-02-06T16:08:18.800 回答
0

我们可以说同伴与 Java 中的“静态块”相同,但在 Kotlin 的情况下,没有静态块的概念,同伴进入框架。

如何定义伴随块:

class Example {
      companion object {
        fun display(){
        //place your code
     }
  }
}

同伴块的调用方法,直接带类名

Example.Companion.display
于 2019-01-29T17:43:46.383 回答