“伴侣对象”的本意是什么?到目前为止,我一直在使用它来static
在需要时替换 Java。
我很困惑:
- 为什么叫“伴侣”?
- 这是否意味着要创建多个静态属性,我必须在块内将它们组合在一起
companion object
? - 为了立即创建一个作用域为类的单例实例,我经常写
:
companion object {
val singleton by lazy { ... }
}
这似乎是一种单一的方式。有什么更好的方法?
“伴侣对象”的本意是什么?到目前为止,我一直在使用它来static
在需要时替换 Java。
我很困惑:
companion object
?:
companion object {
val singleton by lazy { ... }
}
这似乎是一种单一的方式。有什么更好的方法?
“伴侣对象”的本意是什么?为什么叫“伴侣”?
首先,Kotlin 没有使用 Java 的static
成员概念,因为 Kotlin 有自己的s概念object
来描述与单例状态相关的属性和函数,并且static
类的 Java 部分可以优雅地表示为单例:它是一个单例对象可以通过类名来调用。因此命名:它是一个类附带的对象。
它的名字以前是class object
anddefault object
,后来改名为companion object
which 更清晰,也与Scala 伴生对象一致。
除了命名之外,它比 Javastatic
成员更强大:它可以扩展类和接口,并且您可以像其他对象一样引用和传递它。
这是否意味着要创建多个静态属性,我必须在
companion object
块内将它们组合在一起?
是的,这是惯用的方式。或者您甚至可以按照它们的含义将它们分组到非伴随对象中:
class MyClass {
object IO {
fun makeSomethingWithIO() { /* ... */ }
}
object Factory {
fun createSomething() { /* ... */ }
}
}
为了立即创建一个作用域为类的单例实例,我经常编写
/*...*/
这似乎是一种单一的做法。有什么更好的方法?
这取决于您在每种特定情况下需要什么。您的代码非常适合存储绑定到在第一次调用它时初始化的类的状态。
如果您不需要它与类连接,只需使用对象声明:
object Foo {
val something by lazy { ... }
}
您还可以删除lazy { ... }
委托以使属性在第一类的使用情况下初始化,就像 Java 静态初始化器一样
您可能还会发现初始化单例状态的有用方法。
为什么叫“伴侣”?
该对象是实例的伴侣。IIRC 在这里进行了长时间的讨论:即将到来的更改类对象重新思考
这是否意味着要创建多个静态属性,我必须将其组合在伴生对象块中?
是的。每个“静态”属性/方法都需要放在这个伴侣中。
为了立即创建一个作用域为类的单例实例,我经常写
您不会立即创建单例实例。它是在singleton
第一次访问时创建的。
这似乎是一种单一的方式。有什么更好的方法?
而是去object Singleton { }
定义一个单例类。请参阅:对象声明
您不必创建 的实例Singleton
,只需像这样使用它Singleton.doWork()
请记住,Kotlin 提供了其他东西来组织您的代码。现在有简单静态函数的替代方法,例如您可以使用顶级函数。
为什么叫“伴侣”?
类中的对象声明可以用伴随关键字标记:
class MyClass {
companion object Factory {
fun create(): MyClass = MyClass()
}
}
可以通过简单地使用类名作为限定符来调用伴生对象的成员:
val instance = MyClass.create()
如果你只使用'object'而不使用'companion',你必须这样做:
val instance = MyClass.Factory.create()
在我的理解中,'companion' 意味着这个对象与外部类是相伴的。
当具有相关功能的类/对象属于一起时,它们就像彼此的伴侣。在这种情况下,同伴是指合伙人或合伙人。
更干净的顶级命名空间
当某个独立函数打算仅与某个特定类一起使用时,我们不是将其定义为顶级函数,而是在该特定类中定义它。这可以防止顶级命名空间的污染,并有助于 IDE 提供更多相关的自动完成提示。
包装方便
当类/对象在它们提供给彼此的功能方面彼此密切相关时,将它们保持在一起很方便。我们省去了将它们保存在不同文件中并跟踪它们之间关联的工作。
代码可读性
仅通过查看伙伴关系,您就会知道这object
为外部类提供了帮助器功能,并且可能不会在任何其他上下文中使用。因为如果要与其他类一起使用,它将是一个单独的顶级class
或object
或函数。
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)
}
虽然这是一个很好的设置,但其中存在几个问题:
UserAccess
添加/删除User
.UserAccess
可以创建我们不想要的多个实例。我们只需要在整个应用程序中进行一次数据访问object
(单例) 。User
UserAccess
与其他类一起使用或扩展。所以,它并没有让我们的意图明确我们想要做什么。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 object
和lazy { }
该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")
}
}
}
在这段代码中,获取list
和settings
是昂贵的操作。因此,我们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 object
是Companion
,如果您不指定它。
用法:
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 的习惯。
我们可以说同伴与 Java 中的“静态块”相同,但在 Kotlin 的情况下,没有静态块的概念,同伴进入框架。
如何定义伴随块:
class Example {
companion object {
fun display(){
//place your code
}
}
}
同伴块的调用方法,直接带类名
Example.Companion.display