Kotlin Tutorial(19)集合與泛型 by Michael | CodeData
top

Kotlin Tutorial(19)集合與泛型

分享:

Kotlin Tutorial(18)泛型的設計與應用 << 前情

應用程式需要保存與處理比較大量的資料,在數量固定的時候可以使用陣列,如果資料的數量不是固定的,而且也會經常執行元素的新增、移除或排序,就應該使用集合(Collection)API保存與處理資料。

認識集合API

應用程式依照資料的需求,使用下面的集合API:

  • Collection:基本的集合API。
    • Set:保存的元素不會重複,不會記錄順序。
      • HashSet:基本的Set類別。
      • TreeSet:依照元素大小排序。
    • List:保存的元素可以重複,每一個元素都有一個索引編號。
      • ArrayList:基本的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瀏覽與下載。

https://github.com/macdidi5/Kotlin-Tutorial

分享:
按讚!加入 CodeData Facebook 粉絲群

相關文章

留言

留言請先。還沒帳號註冊也可以使用FacebookGoogle+登錄留言