# 【Kotlin】data class

### Data Class vs 一般 Class

1. **自動生成的方法**
    
    
    - **Data Class：** Kotlin 編譯器會自動生成 `equals()`、`hashCode()`、`toString()`、`copy()` 等方法。
    - **一般 Class：** 需要手動實現這些方法，或者使用 IDE 的插件來生成。
2. **不可變性**
    
    
    - **Data Class：** 可以聲明為 `val` 型，使其成員變量成為只讀。
    - **一般 Class：** 需要額外的設計來實現不可變性，如使用 `private` 成員變量並提供 `getter` 方法。
3. **Component 函数**
    
    
    - **Data Class：** Kotlin 可以通過 `componentN()` 函數來分解對象。
    - **一般 Class：** 需要手動定義這些分解函數。

---

#### **自動生成的方法**

在這個示例中，`Person` 是一個 data class，它自動獲得了 `equals()`、`hashCode()`、`toString()`、`copy()` 等方法，而一般 class，我們需要手動實現方法。

#####  .toString()

```JavaScript
// Data Class
data class Person(val name: String, val age: Int)

// 一般 Class
class Person2(val name: String, val age: Int)

class Person3(val name: String, val age: Int) {
  override fun toString(): String {
  return "Person3(name='$name', age=$age)"
  }
}

fun main() {
    val p1 = Person("Mary", 18)
    val p2 = Person2("John", 24)
    val p3 = Person3("Sam", 30)
    println(p1) //Person(name=Mary, age=18)
    println(p2) //Person2@27bc2616
    println(p3) //Person3(name='Sam', age=30)
}
```

##### .equals()

```JavaScript
data class Person(val name: String){
    var age:Int = 0
}

data class Person2(val name: String, val age:Int)

class Person3(val name: String, val age:Int){
}

class Person4(val name: String, val age:Int){
    fun equals(p: Person4):Boolean{
        return (this.name == p.name)
    }
}

fun main() {
    
    val jack = Person("Jack")
    jack.age = 20
    println(jack)		// Person(name=Jack)
    println(jack.age)   // 20
    
    val sam = Person("Sam")
    println(sam)			  // Person(name=sam)
    sam.age = 21	
    println(sam.age)		  // 21
    println(sam.equals(jack)) // false
    
    val oldSam = Person("Sam")
    println(oldSam)				// Person(name=sam)
    oldSam.age = 18				
    println(oldSam.age)			// 18
    println(oldSam.equals(sam)) // true !!!
    
    val mary = Person2("Mary",18)
    val mary2 = Person2("Mary",18)
    val mary3 = Person2("Mary",20)
    println(mary.equals(mary2)) // true
    println(mary.equals(mary3)) // false
    
    val john = Person3("John",35)
    val john2 = Person3("John",35)
    println(john.equals(john2))	// false

    val Jennifer = Person4("Jennifer",12)
    val Jennifer2 = Person4("Jennifer",12)
    println(Jennifer.equals(Jennifer2)) // true
}
```

##### .copy()

```JavaScript
data class Person(val name: String, val age: Int)

fun main() {
    val jack = Person("Jack",4)
    println(jack)						// Person(name=Jack, age=4)
	val olderJack = jack.copy(age = 9)
    println(olderJack)					// Person(name=Jack, age=9)
}
```

---

#### **不可變性** 

- 可以聲明為 `val` 型，使其成員變量成為只讀。

```JavaScript
data class Person(val name: String, val age: Int)
fun main() {
   val person = Person("Alice", 30)
   
   person.name = "John" // 'val' cannot be reassigned.
}

```

- 一般class 需撰寫 private set

```JavaScript
class Person {
    var name: String = "Unknown"
        private set // 私有的 setter
    var age: Int = 0
        private set // 私有的 setter
    
    fun setName(newName: String) {
        name = newName // 可以在類內部修改
    }

    fun setAge(newAge: Int) {
        age = newAge // 可以在類內部修改
    }
}

fun main() {
    val person = Person()
    person.setName("Mary")
    person.setAge(18)
    println(person.name) // Mary
    println(person.age)	 // 18
    person.name = "Mary" // Cannot access 'name': it is private in '/Person'.
}
```

---

#### **Component 函数**

在 Kotlin 中，`componentN()` 函數是一種特殊的函數，用於解構（分解）對象。解構是一種語法糖，可以讓你將對象的屬性拆分成獨立的變量。這些函數在 `data class` 中是自動生成的，而在一般的 `class` 中則需要手動定義。

##### 什麼是解構（Destructuring）？

解構（Destructuring）是一種語法特性，允許將對象的屬性分解成獨立的變量。這在需要從對象中提取多個值時非常有用。

##### Data Class 的 Component 函數

當你定義一個 `data class` 時，Kotlin 會自動為每個屬性生成 `componentN()` 函數。這些函數按照屬性在主構造函數中的聲明順序編號。例如，`component1()` 對應於第一個屬性，`component2()` 對應於第二個屬性，以此類推。

```JavaScript
data class Person(val name: String, val age: Int)

fun main() {
    val person = Person("Alice", 30)
    
    // 使用解構聲明
    val (name, age) = person
    println("Name: $name, Age: $age")
    
    // 手動調用 component 函數
    val nameComponent = person.component1()
    val ageComponent = person.component2()
    println("Name: $nameComponent, Age: $ageComponent")
}

```

在這個示例中，`Person` 類是一個 `data class`，所以 Kotlin 會自動生成 `component1()` 和 `component2()` 函數。你可以使用解構聲明 `val (name, age) = person` 來將對象的屬性分解成獨立的變量。

##### 一般 Class 的 Component 函數

對於一般的 `class`，你需要手動定義 `componentN()` 函數來實現解構。這意味著你需要自己編寫這些函數，以便在解構時可以使用。

```JavaScript
class Car(val brand: String, val year: Int) {
    operator fun component1() = brand
    operator fun component2() = year
}

fun main() {
    val car = Car("Toyota", 2022)
    
    // 使用解構聲明
    val (brand, year) = car
    println("Brand: $brand, Year: $year")  // 輸出：Brand: Toyota, Year: 2022
    
    // 手動調用 component 函數
    val brandComponent = car.component1()
    val yearComponent = car.component2()
    println("Brand: $brandComponent, Year: $yearComponent")  // 輸出：Brand: Toyota, Year: 2022
}

```

---

##### 賦值解構

```JavaScript
val jane = User("Jane", 35)
val (name, age) = jane
println("$name, $age years of age")
// Jane, 35 years of age

```

javascript 也有類似功能

```JavaScript
const user = {
name: "Jane",
age: 35,
};

// { name, age } 等同於 { name: name, age: age }
const { name, age } = user;
console.log(`${name}, ${age} years of age`)
// "Jane, 35 years of age"
```

---

```JavaScript
/**
operator 除了 componentN 函數外，operator 關鍵字還可以用於重載許多其他運算符
算術運算符
   + 對應於 plus
   - 對應於 minus
   * 對應於 times
   / 對應於 div
   % 對應於 rem 或 mod
*/
class Show(val liveId: String, val duration: Int, val salesVolume: Int ) {
    operator fun plus(show: Show): Show {
        return Show(liveId, duration + show.duration, salesVolume + show.salesVolume)
    }
}

fun main() {
    val show1 = Show("2024071001001001P", 60, 200)
    val show2 = Show("2024071001001001P", 90, 400)
    val totalShow = show1 + show2 // 使用重載的加法運算符
    println("(${show1.duration}, ${show1.salesVolume})") // (60, 200)
    println("(${show2.duration}, ${show2.salesVolume})") // (90, 200)
    println("(${totalShow.duration}, ${totalShow.salesVolume})") // (150, 200)
}
```

---

#### 建立data class注意事項

為了確保產生的程式碼的一致性和有意義的行為，資料類別必須滿足以下要求：

- 主構造函數必須至少有一個參數。
- 所有主要建構函數參數必須標記為`val`或`var`。
- 資料類別不能是抽象的(abstract)、開放的(abstract)、密封的(sealed)或內部的(inner)。
- 不能直接繼承自其他 `data class`，但可以繼承自普通class或實作介面

此外， data class 的產生遵循以下關於成員繼承的規則：

- 如果data class 本身有`.equals()`、`.hashCode()`、`.toString()` 明確實作或父類別有的實作，則不會產生這些函數，而是使用現有的實作。
- 不允許為`.componentN()`和`.copy()` 函數提供實作。

---

#### data class toString 改用 json 輸出

**導入 Gson 庫**：首先需要將 Gson 添加到你的專案依賴中。

```JavaScript
dependencies {
    implementation 'com.google.code.gson:gson:2.8.8'
}
```

覆寫toString()

```JavaScript
import com.google.gson.Gson

data class Person(val name: String, val age:Int){
    override fun toString():String{
        val gson = Gson()
        return gson.toJson(this)
    }
}

fun main(args: Array<String>) {
    val mary = Person("Mary",18)
    println(mary)
}
// {"name":"Mary","age":18}
```