在前面幾天講到了 Kotlin immutable 3劍客,今天要來講最後一個 Data class 的 copy.
先複習一下 immutable 三劍客
這邊說的不可變的 data class 指的是把屬性都宣告成 val,如以下程式碼,這樣 new 出來的 user object 不能直接改動屬性
data class User(val firstName: String, val lastName: String, val age: Int)
因為不可變的 object 在內部狀態上不會發生變化,就像 String 或 Int 一樣。除了前面提到的為何我們偏好較少的可變性之外,不可變 object 還有它們自己的優點:
我們可以使用不可變對象當作 Map 中的 key 或是放心加入 Set 當中。因為我們都知道在 Kotlin/JVM 下,這兩種集合都使用 hash table 比較是否重覆
反之,本書極度的不建議以可變的 object 當作 Map Key。因為當我們修改已經在 hash table 中分類的元素時,其 hash 可能不再正確,因此我們可能找不到它
以下程式碼說明了這樣的危險性
val names: SortedSet<FullName> = TreeSet()
val person = FullName("AAA", "AAA")
names.add(person)
names.add(FullName("Jordan", "Hansen"))
names.add(FullName("David", "Blanc"))
print(s) // [AAA AAA, David Blanc, Jordan Hansen]
print(person in names) // true
person.name = "ZZZ"
print(names) // [ZZZ AAA, David Blanc, Jordan Hansen]
print(person in names) // false
如以上程式碼,可變對象更加危險且不可預測
但我們的程式還是要進行 state 的轉變,所以這些不可變對象應該具有所需更改的此對象副本的方法。例如,Int 是不可變的,並且它有許多方法,如 plus 或 minus,這些方法不會修改它,而是返回操作結果的新 Int。同樣的方法可以應用到我們的不可變對象上。例如,假設我們有一個不可變的 User 類,我們需要允許其姓氏更改。我們可以提供withSurname 方法來支持它,該方法產生一個具有特定屬性更改的副本
class User(
val name: String,
val surname: String,
) {
fun withSurname(surname: String) = User(name, surname)
}
編寫此類函數當然是可能的,但如果需要為每一個屬性都寫一個,那會非常繁瑣。此時,資料修飾器(data modifier)便派上用場。它生成的方法之一就是 copy。copy 方法會創建一個新的實例,其中所有屬性默認都與前一個實例相同。同時,也可以指定新的值
data class User(
val name: String,
val surname: String,
)
var user = User("Brandy", "Lin")
user = user.copy(surname = "Chang")
print(user) // User(name=Brandy, surname=Chang)
所以在 Kotlin 中,使用 data class 的 copy 方法對於不可變是十分有善的。使用 copy 方法可以非常容易地生成新的修改過的實例,而原始對象保持不變。此外,copy 也具有以下好處:
今天來介紹少見的慢版 ESCAPE。但很耐聽,也有人說這首是寫給前團員