# kotlin筆記--Data class
在 <code>Kotlin</code> 中,<code>data class</code> 是「專門用來裝資料的類別」,主要目的不是處理商業邏輯,而是保存、傳遞、比較資料,提供了定義資料的結構格式。
可以把 <code>Data Class</code> 想成一張「資料表單」,表單上有固定欄位(屬性)。
<font size=4>主要用於:</font>
- 定義資料的格式
- 判斷兩個物件是否相等
- 把物件轉成字串(方便除錯)
- 複製物件並修改其中一個欄位
<font size=4><b>< <u>範例</u> ></b></font>
```kotlin=
// 宣告一個 Data Class,用來表示使用者資料
data class User(
val id: Int, // 使用者編號
val name: String, // 使用者名稱
val email: String // 使用者信箱
)
```
---
Data Class 的自動生成功能
---
<code>Kotlin</code> 的 <code>Data Class</code> 會依照主建構子中的屬性自動產生部分的重要成員函式
- <code>equals()</code>:用於比較兩個對象是否相等。
- <code>hashCode()</code>:用於生成對象的哈希碼。
- <code>toString()</code>:用於生成對象的字符串表示形式。
- <code>componentN()</code>:用於通過位置訪問對象的屬性(僅適用於具有多個屬性的類)。
- <code id="copy">copy()</code>:快速複製並修改部分屬性。
```kotlin=
data class User(
val id: Int, // 使用者編號
val name: String, // 使用者名稱
val email: String // 使用者信箱
)
val user1 = User(1, "PU", "pu@example.com")
val user2 = User(1, "PU", "pu@example.com")
// 1. 內容比較(Data Class 會自動比較屬性內容)
println(user1 == user2)
// true
// 2. 印出內容
println(user1)
// User(id=1, name=PU, email=pu@example.com)
// 3. 複製並修改
val user3 = user1.copy(email = "newpu@example.com")
// 複製一份 user1,並且修改 user1 的 email
// 4. 解構
val (id, name, email) = user1
// id = 1
// name = "PU"
// email = "pu@example.com"
```
---
Data Class 的限制
---
<code>Kotlin</code> 將 <code>data class</code> 限定為「資料描述工具」,透過限制繼承與自動生成函式,換取一致性、可讀性與安全性。
- 主要建構子至少需要一個參數,且所有參數必須標記為 <code>val</code> 或 <code>var</code>
- 預設是 <code>final</code>,不能標記為 <code>abstract</code>、<code>open</code>、<code>sealed</code> 或 <code>inner</code>
- data class 不能被繼承但可以繼承別人
- 不推薦繼承別人,需要繼承的話最好是使用普通的 Class
- 繼承的限制: 父類必須是 <code>open</code>,且必須在主建構子中呼叫父類建構子
- 父類屬性不會被納入自動生成函式
- 不能被繼承,但可以實作介面
```kotlin=
interface Identifiable {
val id: String
}
data class User(
override val id: String,
val name: String
) : Identifiable
```
---
<code>val</code>/<code>var</code> 的層次關係與 <code>DataClass</code>
---
在實務上,當我們在 [ViewModel](https://hackmd.io/@9YAtszqXS2OLNZOrLY_-Jg/android_ViewModel) 中使用 [LiveData](https://hackmd.io/@9YAtszqXS2OLNZOrLY_-Jg/android_liveData)、[mutableStateOf()](https://hackmd.io/MR6tFrBcSbWvy-8eQA6deg#mutableStateOf-%E8%88%87-ViewModel-%E5%8D%94%E4%BD%9C) 時經常會碰到一個問題 -- 明明變數宣告成 <code>val</code>,為什麼內容還是可以被修改?
>這是因為 <code>val</code> 限制的是「變數本身不能重新指向其他物件」,而不是限制物件內部的屬性不能變更。
<font size=4><b>< <u>以下以一個簡單的 <code>DataClass</code> 和 <code>ViewModel</code> 舉例</u> ></b></font>
<details><summary>範例</summary>
<div>
```kotlin=
// DataClass
data class NameCardState(
val name: String,
)
```
```kotlin=
// ViewModel
class MyViewModel(initialState: NameCardState) : ViewModel() {
private val _state = mutableStateOf(initialState)
val state: State<NameCardState> = _state
// data class 的欄位是 val,不能直接修改
// 必須透過 copy() 產生新物件來替換 _state.value
fun onNameChange(newName: String) {
_state.value = _state.value.copy(name = newName)
}
}
```
<font size=3><b>< <u>以保險箱和護貝名片舉例</u> ></b></font>
><code>_state</code>、<code>_state.value</code>、<code>_state.value.name</code>,就像是保險箱、護貝名片、護貝名片上的字
* 第一次執行 <code>val _state = mutableStateOf("名片")</code> , 就是把一張護貝的名片放進去, 然後 <code>_state</code> 會被視為保險箱, 而這張名片就會被視為 <code>_state.value</code>
* 第二次如果打算執行 <code>val _state = mutableStateOf("名片")</code>, 會被視為打算換掉整個保險箱(<code>val _state</code>)而失敗
* 雖然不能替換保險箱(<code>_state</code>),但是 ==[LiveData](https://hackmd.io/@9YAtszqXS2OLNZOrLY_-Jg/android_liveData)、[mutableStateOf()](https://hackmd.io/MR6tFrBcSbWvy-8eQA6deg#mutableStateOf-%E8%88%87-ViewModel-%E5%8D%94%E4%BD%9C) 有提供我們一個 <code>.value 屬性</code> 使我們可以替換保險箱內部的護貝名片(<code>_state.value</code>)==
* 但是由於這張名片(<code>_state.value</code>)上的名子(<code>_state.value.name</code>)是 <code>val</code>(有護貝), 所以名片上的字不能改
</div>
</details>