Kotlin 陣列 (Arrays)

陣列 (Array) 是一種容器,用來存放多個相同型別的資料。 在 Kotlin 中,陣列由 Array 類別表示。它與 Java 的 Array 相容,但功能更強大。

建立陣列

1. 使用 arrayOf()

這是最簡單的建立方式:

val numbers = arrayOf(1, 2, 3)          // Array<Int>
val strings = arrayOf("A", "B", "C")    // Array<String>
val mixed   = arrayOf(1, "A", true)     // Array<Any>

2. 基本型別陣列 (Primitive Type Arrays)

為了效能考量 (避免 Boxing Overhead),Kotlin 提供了針對 Primitive Types 的專用陣列類別: IntArray, ByteArray, ShortArray, CharArray, LongArray ... 等等。

Array<Int>IntArray 是不一樣的!前者是物件陣列 (Integer[]),後者是原生陣列 (int[])。通常建議優先使用 原生陣列,效能較好。
val intArr = intArrayOf(1, 2, 3)           // 使用常用函式建立
val zeros = IntArray(5)                    // 建立長度為 5,初始值為 0 的陣列
val initArr = IntArray(5) { i -> i * 2 }   // [0, 2, 4, 6, 8] (使用 lambda 初始化)

3. 使用建構子 (Constructor)

如果你想要建立一個非 Primitive 且有特定規則的陣列:

// 建立一個長度 5 的 String Array,內容是 index 的字串版
val arr = Array(5) { i -> "Item $i" } 
// ["Item 0", "Item 1", "Item 2", "Item 3", "Item 4"]

4. 空值陣列

如果你需要一個初始化全為 null 的陣列:

val nulls = arrayOfNulls<String>(5) // [null, null, null, null, null]

存取與修改

使用 [] 運算子 (Indexing Operator) 來存取元素。陣列是 Mutable (可變的),所以可以修改內容。

val arr = arrayOf(1, 2, 3)

println(arr[0]) // 1 (Get)
arr[0] = 10     // 修改 (Set)
println(arr[0]) // 10

[] 其實是呼叫了 get()set() 方法的操作符重載。

走訪陣列 (Traversal)

1. 直接走訪元素 (for-each)

for (num in arr) {
    println(num)
}

2. 走訪索引 (indices)

for (i in arr.indices) { // 0..arr.size-1
    println("Index $i is ${arr[i]}")
}

3. 同時取得索引與值 (withIndex)

for ((index, value) in arr.withIndex()) {
    println("Index $index: $value")
}

常見操作與方法

串接 (plus / +)

陣列可以使用 + 號來串接,但注意這會 產生新的陣列

val a = arrayOf(1, 2)
val b = arrayOf(3, 4)
val c = a + b // [1, 2, 3, 4]

切片 (slice)

取出陣列的一部份:

val arr = arrayOf(0, 1, 2, 3, 4, 5)
val sub = arr.sliceArray(1..3) // [1, 2, 3]

排序與反轉

val unsorted = arrayOf(5, 2, 8, 1)

unsorted.sort()         // 原地排序 (In-place sort)
println(unsorted.contentToString()) // [1, 2, 5, 8]

unsorted.reverse()      // 原地反轉

統計 (Sum, Max, Min)

適用於數值陣列:

val nums = intArrayOf(10, 20, 30)
println(nums.sum())       // 60
println(nums.average())   // 20.0
println(nums.maxOrNull()) // 30

函式式操作 (Functional API)

雖然 Array 不是 Collection,但它支援幾乎所有 Collection 的操作:

val arr = arrayOf(1, 2, 3, 4, 5)

// 映射 (Transform)
val double = arr.map { it * 2 } // [2, 4, 6, 8, 10] (回傳 list)

// 過濾 (Filter)
val even = arr.filter { it % 2 == 0 } // [2, 4] (回傳 list)

// 檢查
val hasFive = arr.any { it == 5 } // true
val allPositive = arr.all { it > 0 } // true

多維陣列 (Multi-dimensional Arrays)

Kotlin 沒有直接支援 int[][] 這種語法,多維陣列其實就是「陣列的陣列」(Array of Arrays)。

// 建立一個 2x3 的矩陣
// [
//   [0, 0, 0], 
//   [0, 0, 0]
// ]
val matrix = Array(2) { IntArray(3) }

matrix[0][0] = 1
println(matrix[0][0]) // 1

可變參數 (Varargs) 與 Spread Operator (*)

如果函式接收 vararg 參數,你可以傳入多個值,也可以把現有的陣列「攤平」傳進去 (使用 *):

fun printAll(vararg strings: String) {
    for (s in strings) println(s)
}

fun main() {
    printAll("A", "B", "C")
    
    val arr = arrayOf("D", "E")
    // printAll(arr) // 錯誤!型別不符
    printAll(*arr)   // 正確!使用 * (Spread Operator) 展開陣列
}

陣列內容比較 (Array Equality)

這是最容易犯錯的地方!在 Kotlin (以及 Java) 中,陣列的 == 運算子比較的是 參考 (Reference),也就是「它們是不是同一個物件」,而不是比較內容。

如果要比較兩個陣列的 內容 是否相同,必須使用 .contentEquals()

val a = intArrayOf(1, 2, 3)
val b = intArrayOf(1, 2, 3)

println(a == b)             // false (不同的物件)
println(a.contentEquals(b)) // true  (內容相同)

如果是多維陣列,則要使用 .contentDeepEquals()

val m1 = arrayOf(intArrayOf(1, 2), intArrayOf(3, 4))
val m2 = arrayOf(intArrayOf(1, 2), intArrayOf(3, 4))

println(m1.contentEquals(m2))     // false (因為裡面存的是 IntArray 物件,參考不同)
println(m1.contentDeepEquals(m2)) // true  (深入比較內容)

使用 Array 或 Collection 的時機?

一般建議優先使用 集合 Collections(List, Set, Map) 而不是 Array,原因:

  1. 支援唯讀 (Read-only):集合可以是唯讀的(例如 List<T>),這提供了更好的控制權。讓你可以編寫意圖清晰、更穩健的程式碼。
  2. 元素操作的效率:集合介面可以輕鬆地新增或移除元素。陣列的大小是固定的。每次新增或移除元素時,都必須建立一個全新的陣列,這在工作效率上是非常低下的。
  3. 結構相等性檢查:集合可以直接使用相等運算子 == 來檢查兩個集合在結構上是否相等(即它們的內容是否相同)。Arrays 不能對陣列使用 == 運算子進行結構相等性檢查。你必須改用特殊函式來比較陣列的內容。

適合使用 Array 的情境:

  1. 極致性能需求:因為陣列在記憶體中是連續儲存的,存取速度通常比 List 稍快(因為 List 實現上可能帶有額外開銷)。另外,Kotlin 提供了專門用於原始型別的陣列,例如 IntArray, BooleanArray, DoubleArray 等,這些陣列在底層避免了裝箱/拆箱 (Boxing/Unboxing) 的性能開銷。
  2. 建構自訂資料結構:你需要從頭開始建立客製化的資料結構。