Kotlin:class 中对象和伴生对象的区别

Kotlin: Difference between object and companion object in a class

kotlin 中 class 中的对象和伴随对象有什么区别?

示例:

class MyClass {

    object Holder {
        //something
    }

    companion object {
        //something
    }
}

我已经读到,如果包含的 parameters/methods 与其 class 密切相关,则应使用伴随对象。

但是为什么在class中也可以声明一个普通对象呢?因为它的行为和同伴完全一样,但它必须有一个名字。

它的 "static"(我来自 java 一方)生命周期是否可能有所不同?

伴生对象的存在是因为您可以调用伴生对象的 functions/properties,就像它是一个 java 静态 method/field。为什么你的 Holder 被允许,好吧,没有理由声明嵌套对象是非法的。有时可能会派上用场。

对象可以实现接口。在 class 中,定义一个不实现任何接口的简单对象在大多数情况下没有任何好处。但是,定义实现各种接口的多个对象(例如 Comparator)可能非常有用。

就生命周期而言,伴生对象与在 class 中声明的命名对象没有区别。

有两种不同类型的 object 用法,表达式声明.

对象表达式

当class需要稍微修改时,可以使用对象表达式,但没有必要为其创建一个全新的子class。匿名内部 classes 就是一个很好的例子。

button.setOnClickListener(object: View.OnClickListener() {
    override fun onClick(view: View) {
        // click event
    }
})

需要注意的一件事是匿名内部 classes 可以从封闭范围访问变量,并且这些变量不必是 final。这意味着在未被考虑 final 的匿名内部 class 中使用的变量可以在访问之前意外更改值。

对象声明

对象声明类似于变量声明,因此不能用在赋值语句的右侧。对象声明对于实现单例模式非常有用。

object MySingletonObject {
    fun getInstance(): MySingletonObject {
        // return single instance of object
    }
}

然后可以像这样调用getInstance方法。

MySingletonObject.getInstance()

伴随对象

伴随对象是一种特定类型的对象声明,它允许对象的行为类似于其他语言中的静态对象(例如Java)。向对象声明添加 companion 允许向对象添加“静态”功能,即使 Kotlin 中不存在实际的静态概念。这是一个带有实例方法和伴随方法的 class 示例。

class MyClass {
  companion object MyCompanionObject {
    fun actsAsStatic() {
      // do stuff
    }
  }
  
  fun instanceMethod() {
    // do stuff
  }
}

调用实例方法如下所示。

var myClass = MyClass()
myClass.instanceMethod()

调用伴随对象方法如下所示。

MyClass.actsAsStatic()

有关详细信息,请参阅 Kotlin docs

对象或对象声明在第一次访问时被延迟初始化。

伴随对象在相应的 class 加载时被初始化。它带来了 'static' 本质,尽管 Kotlin 本身并不支持静态成员。

A Companion object 在 class 加载时初始化(通常是第一次被其他正在执行的代码引用)而 Object 声明在第一次访问时延迟初始化。

请参考https://kotlinlang.org/docs/reference/object-declarations.html底部清楚地定义了这两者之间的区别。

Kotlin 实战所述

The object keyword comes up in Kotlin in a number of cases, but they all share the same core idea: the keyword defines a class and creates an instance (in other words, an object) of that class at the same time.

当谈到普通对象和伴生对象时,唯一显着的区别是伴生对象的属性和函数可以通过使用包含 class 的名称直接访问,这使得它看起来像 java 静态成员访问。

例如,如果您有以下 class

class Temp{
    object Holder{
        fun foo() = 1
    }

    companion object{
        fun foo() = "Hello World"
    }
}

然后你可以访问这两个对象如下 来自包含class

foo()   // call to companion object function
Holder.foo() // call to plain object function

并且来自 class

之外
Temp.foo() // call to companion object function
Temp.Holder.foo() // call to plain object function

在幕后,每个对象声明都会创建一个单例。 在伴随对象的情况下,单例对象是在包含 class 的静态初始值设定项中创建的。 但是对于普通对象,单例实例是在第一次访问对象 class 时延迟创建的。

您可以通过编译 kotlin class 然后使用一些 java 反编译器反编译生成的 class 文件来亲眼看到它。

至于为什么还有可能在class中声明一个普通对象,请考虑下面class成员对象非常有用的地方。

data class Employee(val name: String) {
    object NameComparator : Comparator<Employee> {
         override fun compare(p1: Employee, p2: Employee): Int =
             p1.name.compareTo(p2.name)
    }
}

现在我们可以将员工列表排序为

list.sortedWith(Employee.NameComparator))