Kotlin Tutorial(18)泛型的設計與應用 << 前情
應用程式需要保存與處理比較大量的資料,在數量固定的時候可以使用陣列,如果資料的數量不是固定的,而且也會經常執行元素的新增、移除或排序,就應該使用集合(Collection)API保存與處理資料。
認識集合API
應用程式依照資料的需求,使用下面的集合API:
- Collection:基本的集合API。
- Set:保存的元素不會重複,不會記錄順序。
- HashSet:基本的Set類別。
- TreeSet:依照元素大小排序。
- List:保存的元素可以重複,每一個元素都有一個索引編號。
- Map:保存鍵/值(key/value)的元素,可以使用key取得對應的value。
- HashMap:基本的Map類別。
- TreeMap:元素依照key的大小排序。
認識Collection
Collection定義許多基本的函式:
- add(E):Boolean:新增參數指定的元素。
- clear():清除所有元素。
- isEmpty():Boolean:是否沒有元素。
- remove(Any):Boolean:移除參數指定的元素。
- size():Int:傳回元素的數量。
如果需要使用Set保存與處理資料,而且元素不可以是null值,使用下面的語法建立Set物件:
val 變數名稱 : HashSet<元素型態> = HashSet()
或
val 變數名稱 = HashSet<元素型態>()
使用下面語法建立的Set物件,元素可以是null值:
val 變數名稱 = HashSet<元素型態?>()
Set是一個繼承自Collection的介面,它有許多實作的類別,HashSet是一個常用與基本的類別,下面是使用HashSet的範例:
/* net.macdidi5.kotlin.tutorial.ch19.HelloSet01.kt */
package net.macdidi5.kotlin.tutorial.ch19
fun main(args: Array<String>) {
// 建立元素型態為String的HashSet物件
val names01 = HashSet<String>()
// 加入String元素
names01.add("Simon")
names01.add("Mary")
names01.add("Sam")
// 編譯錯誤,不可以加入null元素
//names01.add(null)
// 編譯錯誤,不可以加入String型態以外的元素
//names01.add(Item(101, "Hello", "Kotlin"))
// 重複的元素會忽略
names01.add("Simon")
println("names01.size: ${names01.size}")
// 顯示: names01.size: 3
for (n in names01) {
println("Hello01! $n!")
}
// 顯示(與新增的順序可能不一樣):
// Hello01! Simon!
// Hello01! Sam!
// Hello01! Mary!
}
List是一個繼承自Collection的介面,它有許多實作的類別,ArrayList是一個常用與基本的類別,下面是使用ArrayList物件的範例:
/* net.macdidi5.kotlin.tutorial.ch19.HelloList01.kt */
package net.macdidi5.kotlin.tutorial.ch19
fun main(args: Array<String>) {
// 建立元素型態為String的ArrayList物件
val names01 = ArrayList<String>()
names01.add("Simon")
names01.add("Mary")
names01.add("Sam")
names01.add("Simon")
// 不可以加入null元素
//names01.add(null)
// 不可以加入String型態以外的元素
// names01.add(LocalDate.now())
println("names01.size: ${names01.size}")
// 顯示: names01.size: 3
// 呼叫get函式取得參數指定索引編號的元素
println("names01.get(1): ${names01.get(1)}")
// 顯示: names01.get(1): Mary
// 建議使用這樣的作法取得指定索引編號的元素
println("names01[1]: ${names01[1]}")
// 顯示: names01[1]: Mary
for (n in names01) {
println("Hello01! $n!")
}
// 顯示:
// Hello01! Simon!
// Hello01! Mary!
// Hello01! Sam!
// Hello01! Simon!
println()
// 建立元素型態為String的ArrayList物件,元素可以接受null
val names02 : ArrayList<String?> = ArrayList()
names02.add("Simon")
names02.add("Mary")
names02.add("Sam")
names02.add("Simon")
names02.add(null)
// 使用List的元素索引編號
for ( (i, n) in names02.withIndex()) {
println("$i : Hello02! $n!")
}
// 顯示:
// 0 : Hello02! Simon!
// 1 : Hello02! Mary!
// 2 : Hello02! Sam!
// 3 : Hello02! Simon!
// 4 : Hello02! null!
}
建立包含元素的集合物件
如果建立集合物件的時候,希望包含一些已知的元素,可以使用下面的Kotlin函式:
- Set
- setOf:建立不可以改變元素的Set物件。
- mutableSetOf:建立可以改變元素的MutableSet物件。
- List
- listOf:建立不可以改變元素的List物件。
- mutableListOf:建立可以改變元素的MutableList物件。
下面是使用setOf與mutableSetOf函式建立Set物件的範例:
/* net.macdidi5.kotlin.tutorial.ch19.HelloSet02.kt */
package net.macdidi5.kotlin.tutorial.ch19
import java.util.TreeSet
fun main(args: Array<String>) {
// 使用setOf函式建立字串元素的Set物件
val names02 : Set<String> = setOf("Simon", "Mary", "Sam")
// 使用setOf建立的Set物件不可以執行元素的異動
// 所以沒有add與delete這類異動的函式
// names02.add("John")
for (n in names02) {
println("Hello02! $n!")
}
println()
// 使用mutableSetOf函式建立字串元素的MutableSet物件
val names03 : MutableSet<String> =
mutableSetOf("Simon", "Mary", "Sam")
// MutableSet物件可以執行元素的異動
names03.add("John")
for (n in names03) {
println("Hello03! $n!")
}
}
下面是使用listOf與mutableListOf函式建立List物件的範例:
/* net.macdidi5.kotlin.tutorial.ch19.HelloList02.kt */
package net.macdidi5.kotlin.tutorial.ch19
fun main(args: Array<String>) {
// 使用listOf函式建立字串元素的List物件
val names03 : List<String> = listOf("Simon", "Mary")
// 使用listOf建立的List物件不可以執行元素的異動
// 所以沒有add與delete這類異動的函式
//names03.add("Sam")
for (n in names03) {
println("Hello03! $n!")
}
println()
// 使用mutableListOf函式建立字串元素的MutableList物件
val names04 : MutableList<String> = mutableListOf("Simon", "Mary")
// MutableList物件可以執行元素的異動
names04.add("Sam")
for (n in names04) {
println("Hello04! $n!")
}
}
排序
應用程式經常有資料排序的需求,例如在購物網站查詢的商品資料,可以讓使用者選擇依照價格或上架日期排列。根據應用程式資料排序的需求,分別單一排序與多多種排序。
單一排序
應用程式的某一種資料,只需要固定一種排序方式,必須讓包裝資料的類別實作Comparable介面:
/* net.macdidi5.kotlin.tutorial.ch19.Item.kt */
package net.macdidi5.kotlin.tutorial.ch19
// 實作Comparable介面
data class Item (val id: Long,
var title: String,
var content: String) : Comparable<Item> {
val details : String
get() {
return "Item(id=$id, title=$title, content=$content)"
}
// 覆寫Comparable介面的抽象函式
// 使用title的字元個數決定大小
// 回傳值:
// 正數:自己比參數大
// 負數:自己比參數小
// 0:一樣大小
override fun compareTo(other: Item) =
title.length - other.title.length
}
已經實作Comparable介面的物件,必須存放在TreeSet物件,裡面的元素才會依照compareTo函式的實作排序,放在一般HashSet物件是不會排序的:
/* net.macdidi5.kotlin.tutorial.ch19.HelloSet03.kt */
package net.macdidi5.kotlin.tutorial.ch19
import java.util.TreeSet
fun main(args: Array<String>) {
// 建立測試用的Item物件
val item01 = Item(101, "Hello", "Kotlin")
val item02 = Item(102, "Hi", "Android")
val item03 = Item(103, "Chao", "Java")
// 跟item01一樣的Item物件
val item04 = Item(101, "Hello", "Kotlin")
// 建立元素型態為Item的TreeSet物件
// 如果元素類別已經實作Comparable介面
// 加入的元素就會依照compareTo函式的實作排列
val items02 = TreeSet<Item>()
// 加入Item元素
items02.add(item01)
items02.add(item02)
items02.add(item03)
// 重複的元素會忽略
items02.add(item04)
for (item in items02) {
println(item.details)
}
// 顯示(依照title的字元個數排序):
// Item(id=102, title=Hi, content=Android)
// Item(id=103, title=Chao, content=Java)
// Item(id=101, title=Hello, content=Kotlin)
println()
// 使用sortedSetOf函式建立字串元素的TreeSet物件
// 如果元素類別已經實作Comparable介面
// 加入的元素就會依照compareTo函式的實作排列
val items03 = sortedSetOf(item01, item02, item03, item04)
for (item in items03) {
println(item.details)
}
// 顯示(依照title的字元個數排序):
// Item(id=102, title=Hi, content=Android)
// Item(id=103, title=Chao, content=Java)
// Item(id=101, title=Hello, content=Kotlin)
}
多種排序需求
如果應用程式的某一種資料,需要多種排序方式,包裝資料的類別不需要實作Comparable介面。根據排序的需求,針對每一種排序方式宣告一個實作Comparable介面的類別,例如下面是提供依照編號排序的類別:
/* net.macdidi5.kotlin.tutorial.ch19.ItemSortId.kt */
package net.macdidi5.kotlin.tutorial.ch19
// 實作Comparable介面
class ItemSortId : Comparator<Item> {
// 覆寫Comparable介面的抽象函式
// 使用id的數字決定大小
// 回傳值:
// 正數:自己比參數大
// 負數:自己比參數小
// 0:一樣大小
override fun compare(o1: Item, o2: Item): Int {
return when {
(o1.id > o2.id) -> 1
(o1.id < o2.id) -> -1
else -> 0
}
}
}
下面是提供依照標題排序的類別:
/* net.macdidi5.kotlin.tutorial.ch19.ItemSortTitle.kt */
package net.macdidi5.kotlin.tutorial.ch19
// 實作Comparable介面
class ItemSortTitle : Comparator<Item> {
// 覆寫Comparable介面的抽象函式
// 使用title的字串內容決定大小
// 回傳值:
// 正數:自己比參數大
// 負數:自己比參數小
// 0:一樣大小
override fun compare(o1: Item, o2: Item): Int {
// 因為String已經實作Comparator介面
// 所以直接呼叫String實作的compareTo函式
return o1.title.compareTo(o2.title)
}
}
完成需要的排序類別以後,物件必須存放在TreeSet物件,不過在建立TreeSet物件的時候,使用排序類別勿件指定排序的方式,裡面的元素就會依照自動排序:
/* net.macdidi5.kotlin.tutorial.ch19.HelloSet04.kt */
package net.macdidi5.kotlin.tutorial.ch19
import java.util.TreeSet
fun main(args: Array<String>) {
// 建立測試用的Item物件
val item01 = Item(101, "Hello", "Kotlin")
val item02 = Item(102, "Hi", "Android")
val item03 = Item(103, "Chao", "Java")
// 建立元素型態為Item的TreeSet物件
// 指定元素的排序方式為ItemSortId
val items04 = TreeSet<Item>( ItemSortId() )
items04.add(item01)
items04.add(item02)
items04.add(item03)
for (item in items04) {
println(item.details)
}
// 顯示(依照id排序):
// Item(id=101, title=Hello, content=Kotlin)
// Item(id=102, title=Hi, content=Android)
// Item(id=103, title=Chao, content=Java)
println()
// 建立元素型態為Item的TreeSet物件
// 指定元素的排序方式為ItemSortTitle
val items05 = TreeSet<Item>( ItemSortTitle() )
items05.add(item01)
items05.add(item02)
items05.add(item03)
for (item in items05) {
println(item.details)
}
// 顯示(依照title的字串內容排序):
// Item(id=103, title=Chao, content=Java)
// Item(id=101, title=Hello, content=Kotlin)
// Item(id=102, title=Hi, content=Android)
}
存放在List裡面的元素只有編號,並不會依照元素的大小排序。不過可以使用List提供的sort與sortWith函式,重新排列元素的順序:
/* net.macdidi5.kotlin.tutorial.ch19.HelloList03.kt */
package net.macdidi5.kotlin.tutorial.ch19
fun main(args: Array<String>) {
// 建立測試用的Item物件
val item01 = Item(101, "Hello", "Kotlin")
val item02 = Item(102, "Hi", "Android!")
val item03 = Item(103, "Chao", "Java")
// 建立元素型態為Item的ArrayList物件
val items = ArrayList<Item>()
// 加入Item元素
items.add(item01)
items.add(item02)
items.add(item03)
println("\n===== Sort by Item.compareTo:")
// 如果元素類別已經實作Comparable介面
// 呼叫sort函式以後,元素就會依照compareTo函式的實作重新排列
items.sort()
for (item in items) {
println(item.details)
}
// 顯示(依照title的字元個數排序):
// Item(id=102, title=Hi, content=Android)
// Item(id=103, title=Chao, content=Java)
// Item(id=101, title=Hello, content=Kotlin)
println("\n===== Sort by id:")
// 呼叫sortWith函式以後,元素就會依照參數的compareTo函式實作重新排列
items.sortWith( ItemSortId() )
for (item in items) {
println(item.details)
}
// 顯示(依照id排序):
// Item(id=101, title=Hello, content=Kotlin)
// Item(id=102, title=Hi, content=Android!)
// Item(id=103, title=Chao, content=Java)
println("\n===== Sort by title:")
// 呼叫sortWith函式以後,元素就會依照參數的compareTo函式實作重新排列
items.sortWith( ItemSortTitle() )
for (item in items) {
println(item.details)
}
// 顯示(依照title的字串內容排序):
// Item(id=103, title=Chao, content=Java)
// Item(id=101, title=Hello, content=Kotlin)
// Item(id=102, title=Hi, content=Android!)
}
Map
如果應用程式保存一些會員資料,可能經常需要使用會員編號取得這個會員的資料,這類的需求使用List或Set資料結構都不太合適,Map的每一個元素都有鍵(key)與值(value),例如使用key儲存會員編號,value儲存對應的會員物件。下面的語法可以宣告與建立Map物件:
val 變數名稱 : HashMap<Key型態, Value型態> = HashMap()
或
val 變數名稱 = HashMap<Key型態, Value型態>()
加入元素到Map物件使用下面的語法:
Map物件變數.put(key, value)
或
Map物件變數[key] = value
Map提供下面其它基本函式:
- get(K key):V:傳回key對應的value,如果key不存在的話就傳回null。
- size():Int:傳回元素的數量。
- clear():清除所有元素。
- isEmpty():Boolean:是否沒有元素。
Map提供下面的常用屬性:
- keys:型態為Set,包含Map所有的key。
- values:型態為Collection,包含Map所有的value。
- entries:型態為Set>,搭配for迴圈處理Map元素。
下面的程式示範Map物件基本的用法:
/* net.macdidi5.kotlin.tutorial.ch19.HelloMap01.kt */
package net.macdidi5.kotlin.tutorial.ch19
import java.util.*
fun main(args: Array<String>) {
// 建立key與value的型態都為String的HashMap物件
val names01 = HashMap<String, String>()
// 呼叫put函式加入元素,第一個參數為key,第二個參數為value
names01.put("S", "Simon")
names01.put("M", "Mary")
// 建議使用類似陣列的語法加入元素
names01["J"] = "John"
names01["S"] = "Sam"
println("names01.size: ${names01.size}")
// 顯示: names01.size: 3
// 呼叫get函式取得與key對應的value
println("names01.get(\"M\"): ${names01.get("M")}")
// 顯示: names01.get("M"): Mary
// 建議使用類似陣列的方式取得與key對應的value
println("names01[\"M\"]: ${names01["M"]}")
// 顯示: names01["M"]: Mary
// 如果指定的key不存在就傳回null值
println("names01.get(\"X\"): ${names01.get("X")}")
println("names01[\"X\"]: ${names01["X"]}")
// 顯示:
// names01.get("X"): null
// names01["X"]: null
// 使用搭配Map物件特殊的for迴圈,讀取所有元素的key與value
for ((key, value) in names01.entries) {
println("$key = $value")
}
// 顯示:
// S = Sam
// J = John
// M = Mary
}
下面的程式示範使用mapOf與mutableMapOf函式建立Map物件:
/* net.macdidi5.kotlin.tutorial.ch19.HelloMap02.kt */
package net.macdidi5.kotlin.tutorial.ch19
import java.util.*
fun main(args: Array<String>) {
// 使用hashMapOf函式建立HashMap物件,可以執行元素的異動
val names02 : HashMap<String, String> = hashMapOf(
"S" to "Simon",
"M" to "Mary",
"J" to "John")
// Map物件的keys屬性傳回所有key,型態為Set<K>
for (key in names02.keys) {
print("$key\t")
}
// 顯示:M J S
println()
// Map物件的values屬性傳回所有value,型態為Collection<V>
for (value in names02.values) {
print("$value\t")
}
// 顯示:Mary John Simon
println("\n")
// 使用mapOf函式建立Map物件,不可以執行元素的異動
val names03: Map<String, String> = mapOf(
"S" to "Simon",
"M" to "Mary",
"J" to "John")
// 使用mapOf建立的Map物件不可以執行元素的異動
// 所以沒有put這類異動的函式
//names03.put("C", "Chris")
// 也不可以使用這樣的語法
//names03["R"] = "Rose"
for ((key, value) in names03.entries) {
println("$key = $value")
}
// 顯示:
// S = Simon
// M = Mary
// J = John
println()
// 使用mutableMapOf函式建立MutableMap物件,可以執行元素的異動
val names04: MutableMap<String, String> = mutableMapOf(
"S" to "Simon",
"M" to "Mary",
"J" to "John")
names04.put("C", "Chris")
names04["R"] = "Rose"
for ((key, value) in names04.entries) {
println("$key = $value")
}
// 顯示:
// S = Simon
// M = Mary
// J = John
// C = Chris
// R = Rose
}
下面的程式示範使用Map儲存物件元素的基本用法:
/* net.macdidi5.kotlin.tutorial.ch19.HelloMap03.kt */
package net.macdidi5.kotlin.tutorial.ch19
import java.util.*
fun main(args: Array<String>) {
val item01 = Item(101, "Hello", "Kotlin")
val item02 = Item(102, "Hi", "Android!")
val item03 = Item(103, "Chao", "Java")
val item04 = Item(103, "Thanks", "Java")
// 建立key的型態為Long,value的型態為Item的HashMap物件
val itemMap = HashMap<Long, Item>()
itemMap[item01.id] = item01
itemMap[item02.id] = item02
itemMap[item03.id] = item03
// key值重複了,覆蓋對應的value
itemMap[item04.id] = item04
for ( (id, item) in itemMap.entries ) {
println("$id = ${item.details}")
}
// 顯示:
// 101 = Item(id=101, title=Hello, content=Kotlin)
// 102 = Item(id=102, title=Hi, content=Android!)
// 103 = Item(id=103, title=Thanks, content=Java)
println()
// 因為如果指定的key不存在,就會傳回null,所以會發生編譯錯誤
//println(itemMap[101].details)
// 必須
println(itemMap[101]?.details)
// 顯示:Item(id=101, title=Hello, content=Kotlin)
println(itemMap[999]?.details)
// 顯示:null
}
下面的程式示範使用TreeMap物件,讓元素依照key的大小排序:
/* net.macdidi5.kotlin.tutorial.ch19.HelloMap04.kt */
package net.macdidi5.kotlin.tutorial.ch19
import java.util.*
fun main(args: Array<String>) {
val item01 = Item(101, "Hello", "Kotlin")
val item02 = Item(102, "Hi", "Android!")
val item03 = Item(103, "Chao", "Java")
val item04 = Item(103, "Thanks", "Java")
// 建立key的型態為Long,value的型態為Item的TreeMap物件
// TreeMap的元素會依照key的大小排列順序
val itemMap02 = TreeMap<String, Item>()
itemMap02[item01.title] = item01
itemMap02[item02.title] = item02
itemMap02[item03.title] = item03
itemMap02[item04.title] = item04
for ( (k, v) in itemMap02.entries ) {
println("$k = ${v.details}")
}
// 顯示:
// Chao = Item(id=103, title=Chao, content=Java)
// Hello = Item(id=101, title=Hello, content=Kotlin)
// Hi = Item(id=102, title=Hi, content=Android!)
// Thanks = Item(id=103, title=Thanks, content=Java)
println()
// 使用sortedMapOf建立SortedMap物件
// SortedMap的元素會依照key的大小排列順序
val itemMap03: SortedMap<String, Item> = sortedMapOf(
item01.content to item01,
item02.content to item02,
item03.content to item03,
item04.content to item04
)
for ( (k, v) in itemMap03.entries ) {
println("$k = ${v.details}")
}
// 顯示:
// Android! = Item(id=102, title=Hi, content=Android!)
// Java = Item(id=103, title=Thanks, content=Java)
// Kotlin = Item(id=101, title=Hello, content=Kotlin)
}
下一步
Java SE 8加入的Lambda,為Java程式設計帶來很大的變化。Kotlin除了支援Lambda外,更進一步提供高階函式(higher-order functions),完整的支援函數式程式設計。
相關的檔案都可以在GitHub瀏覽與下載。
http://github.com/macdidi5/Kotlin-Tutorial
|