1. 簡介
在許多應用程式中,可能都有以清單顯示的資料,例如聯絡人、設定、搜尋結果等。

但在您目前編寫的程式碼中,大多是使用以單一值組成的資料,例如在畫面上顯示的數字或文字。如要建構資料量不固定的應用程式,您需要瞭解如何使用集合。
利用集合類型 (有時稱為資料結構),您就能井然有序地儲存多個值 (通常是資料類型相同的值)。集合可能是已排序的清單、一組不重複的值,或是兩種資料類型的值之間的對應關係。如果能有效運用集合,就可以實作 Android 應用程式的常見功能 (例如捲動清單),並解決與資料量不固定有關的各種實際程式設計問題。
本程式碼研究室討論如何在程式碼中使用多個值,並介紹多種不同的資料結構,包括陣列、清單、組合和對應。
必要條件
- 熟悉 Kotlin 中的物件導向程式設計,包含類別、介面和泛型。
課程內容
- 如何建立及修改陣列。
- 如何使用
List和MutableList。 - 如何使用
Set和MutableSet。 - 如何使用
Map和MutableMap。
軟硬體需求
- 可存取 Kotlin Playground 的網路瀏覽器。
2. Kotlin 中的陣列
什麼是陣列?
如要將程式中任意數量的值分組,使用陣列是最簡單的做法。
就像一組太陽能板稱為太陽能陣列 (solar array)、學習 Kotlin 可獲得一系列 (an array of) 程式設計工作機會,Array 代表的就是多個值。具體而言,陣列是指一系列具有相同資料類型的值。

- 陣列包含多個稱為「元素」的值,有時也稱為「項目」。
- 陣列中的元素都會排序,可透過索引存取。
什麼是索引?索引是與陣列元素相對應的整數。索引會指出項目與陣列中起始元素之間的距離,這稱為以零為開頭的索引。陣列中第一個元素的索引為 0,則第二個元素的索引為 1,因為它與第一個元素距離一個位置,以此類推。

在裝置記憶體中,陣列中的元素會依序排列儲存。本程式碼研究室不會說明基礎細節,但上述資訊有以下兩項重要涵義:
- 有了索引,就能快速存取陣列元素。您可以依索引隨機存取陣列的任一元素,所需時間也會與隨機存取其他任一元素相同,因此會說陣列具有「隨機存取」機制。
- 陣列有固定大小。也就是說,加入陣列的元素數量無法超過此大小。若在大小為 100 個元素的陣列中嘗試存取索引 100 的元素,系統會擲回例外狀況,因為最高索引為 99 (提醒您,第一個索引是 0,不是 1)。不過,您可以修改陣列中位於索引的值。
如要在程式碼中宣告陣列,請使用 arrayOf() 函式。

arrayOf() 函式會將陣列元素視為參數,然後傳回與所傳入參數相符的陣列類型。由於 arrayOf() 的參數數量不盡相同,這或許會與您看到的其他函式略有不同。如果將兩個引數傳入 arrayOf(),則產生的陣列會包含兩個元素,索引分別是 0 和 1。如果傳入三個引數,則產生的陣列將有 3 個元素,索引是 0 到 2。
現在我們來瞭解陣列的實際運作情形,順便探索一下太陽系吧!
- 前往 Kotlin Playground。
- 在
main()中建立rockPlanets變數。呼叫arrayOf(),然後傳入類型String與四個字串,分別代表太陽系中的岩石行星。
val rockPlanets = arrayOf<String>("Mercury", "Venus", "Earth", "Mars")
- 由於 Kotlin 會使用類型推論,因此您可以在呼叫
arrayOf()時省略類型名稱。在rockPlanets變數下方新增另一個變數gasPlanets,而不將類型傳入角括號。
val gasPlanets = arrayOf("Jupiter", "Saturn", "Uranus", "Neptune")
- 您可以利用陣列執行一些有趣的操作。舉例來說,就像數字類型
Int或Double一樣,您可以同時加入兩個陣列。請建立名為solarSystem的新變數,然後使用加號 (+) 運算子,將新變數設為等於rockPlanets和gasPlanets的結果。結果會是新的陣列包含rockPlanets陣列的所有元素,以及和gasPlanets陣列的元素。
val solarSystem = rockPlanets + gasPlanets
- 執行程式,確認能否正常運作。目前還不應顯示任何輸出內容。
存取陣列中的元素
您可以依索引存取陣列的元素。

這就是下標語法。其中包含三個部分:
- 陣列名稱。
- 左方括號 (
[) 和右方括號 (])。 - 位於方括號中的陣列元素索引。
接下來要利用索引存取 solarSystem 陣列的元素。
- 在
main()中,存取並顯示solarSystem陣列的每個元素。請注意,第一個索引是0,最後一個索引是7。
println(solarSystem[0])
println(solarSystem[1])
println(solarSystem[2])
println(solarSystem[3])
println(solarSystem[4])
println(solarSystem[5])
println(solarSystem[6])
println(solarSystem[7])
- 執行程式。元素的順序與您呼叫
arrayOf()時的順序相同。
Mercury Venus Earth Mars Jupiter Saturn Uranus Neptune
您也可以透過索引設定陣列元素的值。

存取索引的方法與先前相同:先是陣列名稱,然後是包含索引的左方括號和右方括號。然後是指派運算子 (=) 和一個新的值。
現在來練習修改 solarSystem 陣列的值。
- 我們可以為火星重新命名,供未來的人類移民使用。請存取索引
3的元素,然後設為"Little Earth"。
solarSystem[3] = "Little Earth"
- 顯示索引
3的元素。
println(solarSystem[3])
- 執行程式。陣列的第四個元素 (位於索引
3) 已更新。
... Little Earth
- 現在,假設科學家發現海王星之後的第九顆行星,稱為「冥王星」。我們先前提過,陣列的大小無法調整。如果嘗試調整會怎麼樣?我們來嘗試將冥王星新增至
solarSystem陣列。新增冥王星至索引8,因為這是陣列中的第 9 個元素。
solarSystem[8] = "Pluto"
- 執行程式碼。系統會擲回
ArrayIndexOutOfBounds例外狀況。由於陣列已有 8 個元素,因此如同預期,您無法直接加入第 9 個元素。
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 8 out of bounds for length 8
- 移除在陣列中加入的冥王星。
要移除的程式碼
solarSystem[8] = "Pluto"
- 如要讓陣列大小大於現有陣列,您需要建立新陣列。定義名為
newSolarSystem的新變數,如下所示。此陣列可儲存九個元素,而非八個。
val newSolarSystem = arrayOf("Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune", "Pluto")
- 現在嘗試顯示索引
8的元素。
println(newSolarSystem[8])
- 執行程式碼,然後就會發現程式碼執行時沒有任何例外狀況。
... Pluto
做得好!現在您對陣列已有所瞭解,可以利用集合執行幾乎所有操作。
等等,不是馬上開始!雖然陣列是程式設計的基本面向之一,但如果工作需要新增和移除元素、確保集合內容不重複或將物件對應至其他物件,使用陣列並不是簡單直接的做法,而且應用程式程式碼會很快變成一團亂。
因此,大部分的程式設計語言 (包括 Kotlin) 都採用特殊的集合類型,處理實際應用程式中常見的情況。以下各節說明三種常用的集合:List、Set 和 Map。此外,您也會瞭解常見的屬性和方法,以及這些集合類型的使用情境。
3. 清單
清單是經過排序且可調整大小的集合,通常是實作為可調整大小的陣列。陣列填滿時,如果嘗試插入新元素,系統會將陣列複製到較大的新陣列。

有了清單,您也可以在其他元素之間,於特定索引插入新元素。

這就是能在清單中新增與移除元素的方式。大多數情況下,無論清單元素的數量為何,在清單中加入任何元素所需的時間都一樣。每隔一段時間,如果加入新元素會導致陣列超過定義的大小,就可能必須移動陣列元素,才能為新元素騰出空間。清單會自動完成上述所有操作,但實際上只是系統在需要時將陣列換新而已。
List 和 MutableList
您將在 Kotlin 中看到的集合類型會實作一或多個介面。如本單元先前的「泛型、物件和擴充功能」程式碼研究室所述,介面提供一組標準的屬性和方法,以供類別進行實作。實作 List 介面的類別可提供實作內容給 List 介面的所有屬性和方法。MutableList 也是如此。
那麼 List 和 MutableList 有哪些功能?
List這種介面可定義與項目的唯讀排序集合相關的屬性和方法。MutableList會定義修改清單的方法 (例如新增及移除元素),即可擴充List介面。
這些介面僅指定 List 和/或 MutableList 的屬性與方法。每個屬性和方法的實作方法,是由擴充介面的類別決定。如果不是每次都使用,上述以陣列為基礎的實作方法也仍會是您最常用的實作方式,但 Kotlin 也允許其他類別擴充 List 和 MutableList。
listOf() 函式
和 arrayOf() 一樣,listOf() 函式會以項目做為參數,但會傳回 List,而非陣列。
- 從
main()中移除現有程式碼。 - 在
main()中呼叫listOf(),建立名為solarSystem的行星List。
fun main() {
val solarSystem = listOf("Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune")
}
List具有size屬性,可取得清單中的元素數量。顯示solarSystem清單的size。
println(solarSystem.size)
- 執行程式碼。清單大小應為 8。
8
從清單中存取元素
就像陣列一樣,您可以使用下標語法,從 List 存取特定索引的元素。您也可以使用 get() 方法執行相同操作。下標語法和 get() 方法都會使用 Int 做為參數,並傳回該索引的元素。和 Array 一樣,ArrayList 是以零為開頭的索引,因此第四個元素會位於索引 3。
- 使用下標語法顯示位於索引
2的行星。
println(solarSystem[2])
- 呼叫
solarSystem清單中的get(),顯示位於索引3的元素。
println(solarSystem.get(3))
- 執行程式碼。位於索引
2的元素為"Earth",位於索引3的元素則為"Mars"。
... Earth Mars
除了依索引取得元素以外,您也可以使用 indexOf() 方法搜尋特定元素的索引。indexOf() 方法會搜尋清單的特定元素 (以引數形式傳入),然後傳回該元素第一次出現的索引。如果清單中沒有出現該元素,則會傳回 -1。
- 顯示
solarSystem中呼叫indexOf()傳入"Earth"的結果。
println(solarSystem.indexOf("Earth"))
- 呼叫傳入
"Pluto"的indexOf(),然後顯示結果。
println(solarSystem.indexOf("Pluto"))
- 執行程式碼。一個元素與
"Earth"相符,因此顯示索引2。沒有與"Pluto"相符的元素,因此顯示-1。
... 2 -1
使用 for 迴圈疊代清單元素
瞭解函式類型和 lambda 運算式後,就能利用 repeat() 函式多次執行程式碼。
程式設計的常見工作,是對清單中的每個元素執行一次工作。Kotlin 包含一項稱為 for 迴圈的功能,可透過簡潔清楚的語法完成這項作業。這通常稱為透過清單執行「迴圈」,或是「疊代」清單。

如要透過清單執行迴圈,請使用 for 關鍵字,並在後面加上一對左右括號。在括號中加入變數名稱,後方依序加上 in 關鍵字和集合名稱。右括號之後是一對大括弧,您可以在大括弧中加入要針對集合中每個元素執行的程式碼。這就是迴圈「本體」。每次執行這個程式碼,就稱為「疊代」。
in 關鍵字之前的變數不會以 val 或 var 宣告,而是假設為 get-only。命名方式不限。如果清單的名稱為複數 (例如 planets),則變數通常會以單數格式命名 (例如 planet)。將變數命名為 item 或 element 也很常見。
這會用做與集合中目前元素相對應的暫時變數,也就是索引 0 的元素適用於第一次疊代、索引 1 的元素適用於第二次疊代,以此類推,並且可在大括弧中存取。
如要查看此操作,請使用 for 迴圈顯示出各個行星名稱,且每行單獨顯示一個名稱。
- 在
main()中最近呼叫println()的下方,新增for迴圈。在括號中輸入變數planet的名稱,透過solarSystem清單執行迴圈。
for (planet in solarSystem) {
}
- 在大括弧中,使用
println()顯示planet的值。
for (planet in solarSystem) {
println(planet)
}
- 執行程式碼。系統會針對集合中的每個項目執行迴圈本體中的程式碼。
... Mercury Venus Earth Mars Jupiter Saturn Uranus Neptune
在清單中新增元素
只有實作 MutableList 介面的類別,才能新增、移除和更新集合中的元素。如果持續追蹤新發現的行星,您可能會希望能經常在清單中加入元素。建立要新增或移除元素的清單時,您需要明確呼叫 mutableListOf() 函式,而不是 listOf()。
add() 函式有兩個版本:
- 第一個
add()函式有清單中元素類型的單一參數,並且會將其新增至清單結尾。 - 另一版本的
add()有兩個參數。第一個參數對應至要插入新元素的索引。第二個參數則是要新增至清單的元素。
現在來看看這兩個版本的實際操作方式。
- 將
solarSystem的初始化作業變更為呼叫mutableListOf(),而非listOf()。您現在可以呼叫在MutableList中定義的方法。
val solarSystem = mutableListOf("Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune")
- 同樣地,我們可能會想將冥王星分類為行星。在
solarSystem上呼叫add()方法,傳入"Pluto"做為單一引數。
solarSystem.add("Pluto")
- 有些科學家認為,過去曾有一顆名叫特亞的行星,後來與地球相撞並形成月球。在索引
3插入"Theia",介於"Earth"和"Mars"之間。
solarSystem.add(3, "Theia")
更新特定索引的元素
您可以使用下標語法更新現有元素:
- 將索引
3的值更新為"Future Moon"。
solarSystem[3] = "Future Moon"
- 使用下標語法顯示索引
3和9的值。
println(solarSystem[3])
println(solarSystem[9])
- 執行程式碼,驗證輸出內容。
Future Moon Pluto
移除清單中的元素
您可以使用 remove() 或 removeAt() 方法移除元素,例如將元素傳入 remove() 方法,或使用 removeAt() 依索引移除元素。
現在來看看這兩種元素移除方法的實際操作方式。
- 呼叫
solarSystem上的removeAt(),傳入9以取得索引。這樣就能從清單中移除"Pluto"。
solarSystem.removeAt(9)
- 呼叫
solarSystem上的remove(),傳入"Future Moon"做為要移除的元素。系統會搜尋清單,如果找到相符的元素,就會移除該元素。
solarSystem.remove("Future Moon")
List提供contains()方法,如果清單中有元素,就會傳回Boolean。顯示針對"Pluto"呼叫contains()的結果。
println(solarSystem.contains("Pluto"))
- 更精簡的語法是使用
in運算子。您可以使用元素、in運算子和集合,檢查元素是否在清單中。請使用in運算子檢查solarSystem是否包含"Future Moon"。
println("Future Moon" in solarSystem)
- 執行程式碼。這兩個陳述式都應顯示
false。
... false false
4. 組合
組合是沒有特定順序,且不允許重複值的集合。

為什麼可以有這種集合?答案是「雜湊碼」。雜湊碼是由任何 Kotlin 類別的 hashCode() 方法產生的 Int。這可視為 Kotlin 物件的半唯一 ID。物件的任何微幅變更 (例如在 String 中增加一個字元),都會產生大不相同的雜湊值。雖然兩個物件可能會有相同的雜湊碼 (稱為雜湊衝突),但 hashCode() 函式可確保一定程度的唯一性,而在大多數情況下,兩個不同的值各有不重複的雜湊碼。

組合有兩個重要特性:
- 與清單相比,在集合中搜尋特定元素的速度很快,對大型集合而言更是如此。雖然
List的indexOf()需從頭檢查元素,直到找到相符項目為止,但平均來說,不論是第一個或是第幾十萬個元素,檢查元素是否在組合中所需的時間都相同。 - 組合的記憶體用量通常會高於資料數量相同的清單,因為組合通常需要更多陣列索引,而不是資料。
組合的好處是能確保獨特性。如果您正在編寫用來追蹤新行星的程式,可以利用組合這種簡易做法,檢查行星是否為已知行星。由於資料量龐大,通常建議檢查清單中是否存在某個元素,而這需要疊代所有元素。
就像 List 和 MutableList,系統同時有 Set 和 MutableSet。MutableSet 會實作 Set,因此任何實作 MutableSet 的類別都需要實作這兩者。

在 Kotlin 中使用 MutableSet
本範例將使用 MutableSet 示範如何新增及移除元素。
- 從
main()中移除現有程式碼。 - 使用
mutableSetOf()建立名為solarSystem的Set行星。這會傳回MutableSet,其預設實作方法為LinkedHashSet()。
val solarSystem = mutableSetOf("Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune")
- 使用
size屬性顯示組合大小。
println(solarSystem.size)
- 和
MutableList一樣,MutableSet採用add()方法。使用add()方法在solarSystem組合中加入"Pluto"。只需一個參數即可新增元素。組合中的元素不一定有順序,因此沒有索引!
solarSystem.add("Pluto")
- 在新增元素後,顯示組合的
size。
println(solarSystem.size)
contains()函式會使用單一參數,並檢查組合中是否含有指定的元素。如果有,會傳回 true。如果沒有,則會傳回 false。呼叫contains(),檢查"Pluto"是否在solarSystem之中。
println(solarSystem.contains("Pluto"))
- 執行程式碼。大小增加後,現在
contains()會傳回true。
8 9 true
- 如前所述,組合不能包含重複項目。請再次嘗試新增
"Pluto"。
solarSystem.add("Pluto")
- 再次顯示組合大小。
println(solarSystem.size)
- 再次執行程式碼。
"Pluto"已位於組合中,因此無法新增。這次大小就不會增加。
... 9
remove() 函式會使用單一參數,並從集合中移除指定的元素。
- 使用
remove()函式移除"Pluto"。
solarSystem.remove("Pluto")
- 請顯示集合大小,然後再次呼叫
contains(),確認"Pluto"是否仍在組合中。
println(solarSystem.size)
println(solarSystem.contains("Pluto"))
- 執行程式碼。
"Pluto"已不在組合中,且現在的大小是 8。
... 8 false
5. 對應集合
Map 是由索引鍵和值組成的集合。這就是所謂的「對應」,因為不重複的索引鍵會「對應」到其他值。索引鍵和其相關值通常稱為 key-value pair。

對應中的索引鍵都是不重複的,但對應中的值則不一定。兩個不同的索引鍵可以對應到相同的值。舉例來說,"Mercury" 有 0 個衛星,"Venus" 也有 0 個衛星。
依索引鍵從對應中存取值,通常會比在大型清單中搜尋 (例如使用 indexOf()) 更快。
您可以使用 mapOf() 或 mutableMapOf() 函式宣告對應。對應需要兩個以逗號分隔的泛型類型,一個代表索引鍵,另一個代表值。

如果對應具有初始值,也可以使用類型推論。如要在對應中填入初始值,則每個鍵/值組合都包含索引鍵,且後方依序接上 to 運算子和值。請以半形逗號分隔每個鍵/值組合。

以下進一步說明如何使用對應,並介紹一些實用的屬性和方法。
- 從
main()中移除現有程式碼。 - 使用
mutableMapOf()與初始值建立名為solarSystem的對應,如下所示。
val solarSystem = mutableMapOf(
"Mercury" to 0,
"Venus" to 0,
"Earth" to 1,
"Mars" to 2,
"Jupiter" to 79,
"Saturn" to 82,
"Uranus" to 27,
"Neptune" to 14
)
- 就像清單和組合,
Map提供size屬性,內含鍵/值組合的數量。顯示solarSystem對應的大小。
println(solarSystem.size)
- 您可以使用下標語法設定其他鍵/值組合。請將索引鍵
"Pluto"設為5的值。
solarSystem["Pluto"] = 5
- 插入元素後,再次顯示大小。
println(solarSystem.size)
- 您可以使用下標語法取得值。顯示索引鍵
"Pluto"的衛星數量。
println(solarSystem["Pluto"])
- 您也可以使用
get()方法存取值。無論您使用下標語法還是呼叫get(),所傳遞的索引鍵都可能不位於對應中。如果沒有鍵/值組合,系統就會傳回空值。請顯示"Theia"的衛星數量。
println(solarSystem.get("Theia"))
- 執行程式碼。系統應會顯示冥王星的衛星數量。但由於特亞不在對應中,因此呼叫
get()會傳回空值。
8 9 5 null
remove() 方法會移除含有指定索引鍵的鍵/值組合。如果指定的索引鍵不在對應中,也會傳回已移除的值或 null。
- 顯示呼叫
remove()並傳入"Pluto"的結果。
solarSystem.remove("Pluto")
- 如要確認項目是否已移除,請再次顯示大小。
println(solarSystem.size)
- 執行程式碼。移除項目後的對應大小是 8。
... 8
- 下標語法或
put()方法也可以修改現有索引鍵的值。請使用下標語法將木星的衛星數量更新為 78,然後顯示新的值。
solarSystem["Jupiter"] = 78
println(solarSystem["Jupiter"])
- 執行程式碼。現有索引鍵
"Jupiter"的值已更新。
... 78
6. 結語
恭喜!您已瞭解程式設計中的「陣列」這種基本資料類型,也認識多種從陣列擴展而來的便利集合類型,包括 List、Set 和 Map。這些集合類型可用來在程式碼中分類及整理值。在陣列和清單中,您可以依索引快速存取元素;在集合和對應中,則可使用雜湊碼更輕鬆地在集合中尋找元素。在日後的應用程式中,您會發現這些集合類型的使用頻率很高,您瞭解如何加以利用後,也會對您往後的程式設計職涯有幫助。
摘要
- 陣列會儲存類型相同的已排序資料,且具有固定大小。
- 陣列可用於實作許多其他集合類型。
- 清單為可調整大小的排序集合。
- 組合是沒有順序的集合,而且不包含重複項目。
- 對應的運作方式與組合類似,會儲存指定類型的鍵/值組合。