我们无法看到 copy 方法的实现,因为它是在底层生成的,就像通过 data 修饰符生成的其它方法一样,如果我们能看到它,大概就是下面生成 Person 的样子:
// This is how `copy` is generated under the hood by
// data modifier for `Person` class looks like
fun copy(
id: Int = this.id,
name: String = this.name,
points: Int = this.points
) = Player(id, name, points)
// After compilation
val id: Int = player.component1()
val name: String = player.component2()
val pts: Int = player.component3()
基于位置的解构有优点和缺点。最大的优点是我们可以随意命名变量,我们也可以分解我们想要的一切,只要它提供 componentN 函数。 List 和 Map.Entry 都能体现它 :
val visited = listOf("China", "Russia", "India")
val (first, second, third) = visited
println("$first $second $third") // China Russia India
val trip = mapOf(
"China" to "Tianjin",
"Russia" to "Petersburg",
"India" to "Rishikesh"
)
for ((country, city) in trip) {
println("We loved $city in $country")
// We loved Tianjin in China
// We loved Petersburg in Russia
// We loved Rishikesh in India
}
data class FullName(
val firstName: String,
val secondName: String,
val lastName: String
)
val elon = FullName("Elon", "Reeve", "Musk")
val (name, surname) = elon
print("It is $name $surname!") // It is Elon Reeve!
我们需要小心地解构。使用和主构造函数中相同的属性的名称是很有用的,然后,不正确的顺序,IntelliJ / Android Studio 将会显示警告。甚至可以将警告升级为错误。
不要像下面的例子那样分解得到第一个值:
data class User(val name: String)
val (name) = User("John")
这可能会让读者感到困惑,特别是当你在 lambda 表达式中进行分解时:
data class User(val name: String)
fun main() {
val user = User("John")
user.let { a -> print(a) } // User(name=John)
// 不要这样做
user.let { (a) -> print(a) } // John
}
这是有问题的,因为在一些语言中,lambda 表达式中包住参数的括号是可选或必需的。
优先使用 data class 替代元组(tunples)
data class 提供的功能比元组更多。更具体的说,Kotlin 元组只是可序列化的通用数据类型,并且有一个自定义的 toSring 方法:
public data class Pair<out A, out B>(
public val first: A,
public val second: B
) : Serializable {
public override fun toString(): String ="($first, $second)"
}
public data class Triple<out A, out B, out C>(
public val first: A,
public val second: B,
public val third: C
) : Serializable {
public override fun toString(): String = "($first, $second, $third)"
}
val fullName = "Marcin Moskała"
val (lastName, firstName) = fullName.parseName() ?: return
print("His name is $firstName") // His name is Moskała
为了使引用更加安全、函数更加易读,我们应该使用数据类型:
data class FullName(
val firstName: String,
val lastName: String
)
fun String.parseName(): FullName? {
val indexOfLastSpace = this.trim().lastIndexOf(' ')
if(indexOfLastSpace < 0) return null
val firstName = this.take(indexOfLastSpace)
val lastName = this.drop(indexOfLastSpace)
return FullName(firstName, lastName)
}
// Usage
val fullName = "Marcin Moskała"
val (firstName, lastName) = fullName.parseName() ?: return
它的成本几乎为0,并且显著改善了功能:
函数的返回类型是明确的
返回类型更短,更容易传递
如果用户解构的变量名不同,则会显示一个警告
如果不希望这个类在更大范围内使用时,可以限制其可见性,如果你只需要在单个文件或类中用某些本地函数处理使用它,它甚至可以是私有的。我们更值得去使用 data class 而不是元组。 Kotlin 的类成本很低,请不要害怕使用它们。