【问题标题】:Kotlin: Difference between object and companion object in a classKotlin:类中对象和伴随对象之间的区别
【发布时间】:2017-10-04 12:09:09
【问题描述】:

kotlin 类中的对象和伴生对象有什么区别?

例子:

class MyClass {

    object Holder {
        //something
    }

    companion object {
        //something
    }
}

我已经读过,如果包含的参数/方法与其类密切相关,则应使用伴随对象。

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

它的“静态”(我来自 java 端)生命周期是否可能有所不同?

【问题讨论】:

标签: object kotlin companion-object


【解决方案1】:

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

对象表达式

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

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

需要注意的一点是匿名内部类可以从封闭范围访问变量,并且这些变量不必是final。这意味着在匿名内部类中使用的不被视为final 的变量可能会在访问之前意外更改值。

对象声明

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

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

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

MySingletonObject.getInstance()

伴随对象

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

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

调用实例方法如下所示。

var myClass = MyClass()
myClass.instanceMethod()

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

MyClass.actsAsStatic()

请参阅Kotlin docs 了解更多信息。

【讨论】:

  • 谢谢迈克!这应该是答案。
  • 如果伴随对象没有名称,我必须将伴随对象内的方法用作MyClass.MyCompanionObject.actsAsStatic()MyClass.Companion.actsAsStatic()。这是一个新的变化,还是我做错了什么?谢谢。
【解决方案2】:

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

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

【讨论】:

  • AFAIK 初始化顺序有些不同
  • 有什么区别?我猜同伴首先被初始化,因为它绑定到它的类,然后对象被调用?
  • 伴随对象从包含类的静态构造函数初始化,普通对象在第一次访问该对象时延迟初始化。
  • 这个答案不应该是正确的答案。接受这个答案只是误导。这个答案的最后一句话显然是错误的。 就生命周期而言,伴随对象和在类中声明的命名对象之间绝对存在差异,正如上面@llya 所指出的那样。
  • @OluwasegunWahaab 您是否有足够的声誉来编辑答案?欢迎您改进!
【解决方案3】:

正如 Kotlin in Action 所述

在许多情况下,Kotlin 中都会出现 object 关键字,但它们 都具有相同的核心思想:关键字定义一个类并创建 该类的一个实例(换句话说,一个对象)同时 时间。

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

例如,如果您有以下课程

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

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

那么您可以访问这两个对象,如下所示 从包含类

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

在课堂之外

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

在底层,每个对象声明都会创建一个单例。 在伴随对象的情况下,单例对象是在包含类的静态初始化程序中创建的。 但是对于普通对象,在第一次访问对象类时会延迟创建单例实例。

你可以自己编译kotlin类,然后使用一些java反编译器反编译生成的类文件。

至于为什么还有可能在类中声明一个普通对象,考虑下面这个类,其中成员对象非常有用。

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))

【讨论】:

    【解决方案4】:

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

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

    【讨论】:

      【解决方案5】:

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

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

      【讨论】:

        【解决方案6】:

        Companion 对象的存在是因为您可以调用伴随对象的函数/属性,就像它是 java 静态方法/字段一样。以及为什么允许您的Holder,好吧,没有理由声明嵌套对象是非法的。有时它可能会派上用场。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-11-23
          • 2016-05-21
          • 1970-01-01
          • 2017-10-30
          • 1970-01-01
          相关资源
          最近更新 更多