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 仓库地址