1. 概述

克隆对象,简单来说就是创建一个与原始对象内容完全一致的副本。在编程中,这通常意味着创建一个新对象,其字段值与原对象相同。

本文将介绍 Kotlin 中几种克隆对象的方式,包括浅拷贝与深拷贝的实现。

2. 浅拷贝 vs 深拷贝

理解对象克隆之前,首先要明确浅拷贝(Shallow Copy)与深拷贝(Deep Copy)的区别,尤其是在处理嵌套对象或集合时尤为重要。

  • 浅拷贝:仅复制对象本身,但其中引用的其他对象只是复制了引用地址,而非创建新的实例。
  • 深拷贝:不仅复制对象本身,还递归地复制其引用的所有对象,确保新旧对象完全独立。

举个例子,如果一个对象包含多个层级的嵌套结构,仅复制顶层字段就是浅拷贝;而深拷贝会把每一层都完整复制一份。

3. 克隆方式详解

我们以一个包含嵌套结构的模型为例来演示各种克隆方式:

class Address(var street: String, var city: String)
class Person(var name: String, var address: Address)
class Company(var name: String, var industry: String, val ceo: Person, val employees: List<Person>)
class Organization(var name: String, val headquarters: Address, val companies: List<Company>)

示例对象如下:

val organization = Organization("Bekraf", Address("Jalan Medan Merdeka Selatan", "Jakarta"), listOf(companyBasen, companyKotagede))

3.1. 使用 data class 的 copy()

Kotlin 的 data class 自带 copy() 方法,默认是浅拷贝。要实现深拷贝,必须手动复制嵌套对象。

优点:简洁、Kotlin 原生支持
缺点:需手动处理嵌套结构,否则仍是浅拷贝

val clonedOrganization = organization.copy(
  headquarters = organization.headquarters.copy(),
  companies = organization.companies.map { company ->
    company.copy(
      ceo = company.ceo.copy(address = company.ceo.address.copy()),
      employees = company.employees.map { employee ->
        employee.copy(address = employee.address.copy())
      }
    )
  }
)

3.2. 使用 clone()

通过实现 Java 的 Cloneable 接口并重写 clone() 方法,可以实现深拷贝逻辑。

优点:封装性好,调用简单
缺点:需要手动实现每个类的 clone() 方法,维护成本高

data class Address(var street: String, var city: String) : Cloneable {
    public override fun clone() = Address(this.street, this.city)
}

data class Person(var name: String, var address: Address) : Cloneable {
    public override fun clone() = Person(name, this.address.clone())
}

data class Company(var name: String, var industry: String, val ceo: Person, val employees: List<Person>) : Cloneable {
    public override fun clone() = Company(name, industry, ceo.clone(), employees.map { it.clone() })
}

data class Organization(var name: String, val headquarters: Address, val companies: List<Company>) : Cloneable {
    public override fun clone() = Organization(name, headquarters.clone(), companies.map { it.clone() })
}

调用方式非常简单:

val clonedOrganization = organization.clone()

3.3. 使用次构造函数(Secondary Constructor)

通过自定义构造函数实现对象克隆,无需依赖 data class 或 Cloneable 接口。

优点:完全控制拷贝逻辑,不依赖特定语言特性
缺点:实现略繁琐,需为每个类添加构造函数

class Organization(var name: String, val headquarters: Address, val companies: List<Company>) {
    constructor(organization: Organization) : this(
        organization.name,
        Address(organization.headquarters),
        organization.companies.map { Company(it) }
    )
}
// Address、Person、Company 类似处理

调用方式:

val clonedOrganization = Organization(organization)

3.4. 自定义深拷贝方法

如果希望将拷贝逻辑从构造函数中分离出来,可以定义一个 deepCopy() 方法。

优点:灵活,易于扩展
缺点:每个类都要实现 deepCopy 方法,初期开发成本高

class Organization(var name: String, val headquarters: Address, val companies: List<Company>) {
    fun deepCopy(
        name: String = this.name,
        headquarters: Address = this.headquarters.deepCopy(),
        companies: List<Company> = this.companies.map { it.deepCopy() },
    ) = Organization(name, headquarters, companies)
}
// Address、Person、Company 类似处理

调用方式:

val clonedOrganization = organization.deepCopy()

4. 验证是否为深拷贝

为了验证克隆是否成功实现了深拷贝,我们修改原始对象的字段,检查克隆对象是否受到影响。

organization.name = "New Org Name"
organization.headquarters.city = "New City"
organization.companies.first().name = "New Company Name"
organization.companies.first().ceo.name = "New CEO Name"
organization.companies.first().ceo.address.city = "New CEO Address City Name"
organization.companies.first().employees.first().name = "New Employee Name"
organization.companies.first().employees.first().address.city = "New Employee Address City Name"

验证逻辑如下:

assertThat(clonedOrganization).isNotSameAs(organization)
assertThat(clonedOrganization.headquarters.city).isNotEqualTo("New City")
assertThat(clonedOrganization.companies.first().name).isNotEqualTo("New Company Name")
assertThat(clonedOrganization.companies.first().ceo.name).isNotEqualTo("New CEO Name")
assertThat(clonedOrganization.companies.first().ceo.address.city).isNotEqualTo("New CEO Address City Name")
assertThat(clonedOrganization.companies.first().employees.first().name).isNotEqualTo("New Employee Name")
assertThat(clonedOrganization.companies.first().employees.first().address.city).isNotEqualTo("New Employee Address City Name")

所有断言通过,说明克隆逻辑正确。

5. 总结

Kotlin 提供了多种方式实现对象克隆:

方式 说明 适用场景
data class copy() 简洁,但默认浅拷贝 快速克隆简单对象
clone() 可实现深拷贝,但需手动实现 项目中已有 Cloneable 结构
次构造函数 不依赖语言特性,控制灵活 不想使用 data class 或接口
自定义 deepCopy() 灵活但需手动实现 需精细控制拷贝逻辑的场景

选择哪种方式,取决于你的项目结构、性能要求和代码可维护性。对于大多数 Kotlin 项目,推荐使用 data class copy() 并结合手动嵌套拷贝,兼顾简洁与可控性。

完整示例代码请参考:GitHub 仓库地址


原始标题:Cloning an Object in Kotlin