【Kotlin】data class
Data Class vs 一般 Class
-
自動生成的方法
- Data Class: Kotlin 編譯器會自動生成
equals()
、hashCode()
、toString()
、copy()
等方法。 - 一般 Class: 需要手動實現這些方法,或者使用 IDE 的插件來生成。
- Data Class: Kotlin 編譯器會自動生成
-
不可變性
- Data Class: 可以聲明為
val
型,使其成員變量成為只讀。 - 一般 Class: 需要額外的設計來實現不可變性,如使用
private
成員變量並提供getter
方法。
- Data Class: 可以聲明為
-
Component 函数
- Data Class: Kotlin 可以通過
componentN()
函數來分解對象。 - 一般 Class: 需要手動定義這些分解函數。
- Data Class: Kotlin 可以通過
自動生成的方法
在這個示例中,Person
是一個 data class,它自動獲得了 equals()
、hashCode()
、toString()
、copy()
等方法,而一般 class,我們需要手動實現方法。
.toString()
// 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()
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()
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
型,使其成員變量成為只讀。
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
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()
對應於第二個屬性,以此類推。
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()
函數來實現解構。這意味著你需要自己編寫這些函數,以便在解構時可以使用。
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
}
賦值解構
val jane = User("Jane", 35)
val (name, age) = jane
println("$name, $age years of age")
// Jane, 35 years of age
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"
/**
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 本身有
.equals()
、.hashCode()
、.toString()
明確實作或父類別有的實作,則不會產生這些函數,而是使用現有的實作。 - 不允許為
.componentN()
和.copy()
函數提供實作。
data class toString 改用 json 輸出
導入 Gson 庫:首先需要將 Gson 添加到你的專案依賴中。
dependencies {
implementation 'com.google.code.gson:gson:2.8.8'
}
覆寫toString()
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}