[Day 10] 遠征 Kotlin × 泛型 Generic
泛型 Generic 介紹
在 Collections 章節中,我們有提到 List
、Set
等集合用法,眼尖的朋友可能會發現到,在宣告一個新集合時,我們都必須使用 < >
和設定型態來進行宣告, 而這樣的方法其實就是一種泛型(Generic)應用,集合只是一個容器,為我們提供迭代元素、新增元素、刪除元素等操作方法,當我們需求上需要儲存什麼樣別的資料,再為這個容器加上型別即可使用。
可能有朋友會好奇,為什麼會需要使用泛型?假設今天我們有一個集合物件,裡面充滿著各種型別的資料,那我們在引用這個集合時,必然會在程式撰寫上處理許多型別轉型的工作,而型別轉型工作會讓我們程式多了一層轉換工作,所以為了減少不必要的轉型工作,我們可以提前在編譯期間(Compile Time)利用泛型告知此集合或方法屬於哪種型別,也是方便我們在開發時清楚要使用什麼類型的資料進行溝通,所以在型別安全檢查、程式碼品質、開發效率等都會帶來好處。
泛型使用
泛型可以讓我們使用在類別、介面、函數上,我們可以直接使用下面範例來觀察,我們定義一個使用泛型的Person 類別,再定義其他資料類別,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| fun main() { val teacher: Person<Teacher> = Person(Teacher("Eric", "A12345")) val student: Person<Student> = Person(Student("Devin", "B991"))
println("老師姓名: ${teacher.data.name}, 職員編號: ${teacher.data.employeeNumber}") println("學生姓名: ${student.data.name}, 學號: ${student.data.studentID}")
}
class Person<T>(person: T) { var data: T = person }
class Teacher(val name: String, val employeeNumber: String)
class Student(val name: String, val studentID: String)
|
泛型參數我們通常會利用字母 T(英文 Type)表示,若要使用其他名稱也可以,但在支援泛型的程式語言中大多使用 T 來表示,這樣可以讓其他開發者更容易了解我們的程式碼,而泛型還有其他常用的命名,如下:
- E - Element
- K - Key
- N - Number
- T - Type
- V - Value
- R - Return
- S, U, V etc. - 2nd, 3rd, 4th types
多泛型參數
泛型也允許使用多個泛型參數,參數名稱建議可參考上面常見規範,我們可以將上面的範例進行修改,在原本的 Person 類別中加入一個支援多種泛型的函數,如下範例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| fun main() { val teacher: Person<Teacher> = Person(Teacher("Eric", "A12345")) val student: Person<Student> = Person(Student("Devin", "B991"))
println("老師姓名: ${teacher.data.name}, 職員編號: ${teacher.data.employeeNumber}") println("學生姓名: ${student.data.name}, 學號: ${student.data.studentID}")
teacher.speak { println("${teacher.data.name}: 開始上課")} student.speak { println("${student.data.name}: 老師好")} }
class Person<T>(person: T) { var data: T = person
fun <R> speak(func: (T) -> R): R? { return func(data) } }
data class Teacher(val name: String, val employeeNumber: String)
data class Student(val name: String, val studentID: String)
|
多泛型實例操作
上面範例我們都只使用一個資料進行操作,若我們想要一次使用多筆資料,此時可以使用 vararg
關鍵字,讓泛型類別可支援多個參數,參數即為元素陣列,而既然是陣列資料,我們就可以使用索引進行取值,我們可以搭配 get
運算函數進行索引取值動作,如下範例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| fun main() { val teacher: Person<Teacher> = Person(Teacher("Eric", "A12345")) val student: Person<Student> = Person(Student("Devin", "B991"), Student("Jack", "B992"))
println("老師姓名: ${teacher[0]?.name}, 職員編號: ${teacher[0]?.employeeNumber}") println("學生姓名: ${student[0]?.name}, 學號: ${student[0]?.studentID}") println("學生姓名: ${student[1]?.name}, 學號: ${student[1]?.studentID}")
teacher.speak(0) { println("${teacher[0]?.name}: 開始上課")} student.speak(0) { println("${student[0]?.name}: 老師好")} student.speak(0) { println("${student[1]?.name}: 老師好")} }
class Person<T>(vararg person: T) { var data: Array<out T> = person
operator fun get(index: Int): T? = data[index]
fun <R> speak(index: Int, func: (T) -> R): R? { return func(data[index]) } }
data class Teacher(val name: String, val employeeNumber: String)
data class Student(val name: String, val studentID: String)
|
in & out
在前一個範例中我們有用到 out
關鍵字,我們發現若在泛型類別中要將泛型用在內部函數的返回值上,必須加上 out
關鍵字,而 out
關鍵字其實有一個夥伴- in
關鍵字,in
則是將泛型用在函數參數值上。
而泛型參數其實扮演兩種角色:生產者(producer)
或 消費者(consumer)
,若身為生產者時,只能讀不能寫;消費者則相反,不能讀只能寫,而生產者為 out
關鍵字,消費者則為 in
關鍵字。
接下來,我們利用範例來觀察 in
& out
的實際狀況,首先介紹 out
關鍵字, out
泛型可以讓我們將子類別的泛型物件賦值給父類別泛型物件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| fun main() { val producer1 : Production<Food> = FoodStore() val producer2 : Production<Food> = FastFoodStore() val producer3 : Production<Food> = InOutBurger()
}
open class Food open class FastFood : Food() class Burger : FastFood()
interface Production<out T> { fun produce(): T }
class FoodStore : Production<Food> { override fun produce(): Food { println("食品商店") return Food() } }
class FastFoodStore : Production<FastFood> { override fun produce(): FastFood { println("速食商店") return FastFood() } }
class InOutBurger : Production<Burger> { override fun produce(): Burger { println("漢堡商店") return Burger() } }
|
再來是 in
關鍵字的用法,in
泛型可以讓我們將父類別泛型物件賦值給子類別泛型物件,以下是範例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| fun main() { val consumer1 : Consumer<Burger> = PurchaseFood() val consumer2 : Consumer<Burger> = EatFastFood() val consumer3 : Consumer<Burger> = EatBurger()
}
interface Consumer<in T> { fun consume(item: T) }
class PurchaseFood : Consumer<Food> { override fun consume(item: Food) { println("購買食品商品") } }
class EatFastFood : Consumer<FastFood> { override fun consume(item: FastFood) { println("購買速食食物") } }
class EatBurger : Consumer<Burger> { override fun consume(item: Burger) { println("購買漢堡食物") } }
|
Reference