MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Kotlin数组操作详解

2023-05-147.6k 阅读

Kotlin 数组概述

在 Kotlin 中,数组是一种用于存储多个相同类型元素的数据结构。与许多其他编程语言类似,Kotlin 的数组在内存中是连续存储的,这使得对数组元素的访问非常高效。数组的大小在创建时就已经确定,并且一旦创建,大小通常不能改变。

Kotlin 提供了 Array 类来表示数组。要创建一个数组,可以使用 arrayOf() 函数,它接受可变数量的参数并返回一个包含这些参数的数组。例如:

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

这里创建了一个包含整数 15 的数组 numbersarrayOf() 函数会根据传入的参数类型自动推断数组的类型,在这个例子中是 IntArray

如果要创建一个指定大小且元素初始值相同的数组,可以使用 Array 构造函数。例如,创建一个大小为 5 且所有元素初始值为 0Int 数组:

val zeros = Array(5) { 0 }

这里的 Array 构造函数接受两个参数,第一个参数是数组的大小,第二个参数是一个 lambda 表达式,用于初始化每个数组元素。在这个例子中,lambda 表达式 { 0 } 表示每个元素都初始化为 0

基本数组类型

Kotlin 有专门的基本类型数组,如 IntArrayDoubleArrayBooleanArray 等,它们对应于 Java 中的基本类型数组。这些基本类型数组比普通的 Array 类更高效,因为它们避免了装箱和拆箱操作。

创建 IntArray 可以使用 intArrayOf() 函数:

val ints = intArrayOf(10, 20, 30)

类似地,DoubleArray 可以使用 doubleArrayOf() 函数创建:

val doubles = doubleArrayOf(1.5, 2.5, 3.5)

对于 BooleanArray,使用 booleanArrayOf() 函数:

val booleans = booleanArrayOf(true, false, true)

使用基本类型数组在性能敏感的场景下非常重要,比如在进行大量数值计算时,使用 IntArray 比使用 Array<Int> 更高效,因为 Array<Int> 会涉及到 Int 类型的装箱和拆箱操作,而 IntArray 直接存储基本类型的 int 值。

访问数组元素

访问数组元素是通过索引来进行的。数组的索引从 0 开始,到 数组大小 - 1 结束。例如,对于前面创建的 numbers 数组:

val numbers = arrayOf(1, 2, 3, 4, 5)
val firstNumber = numbers[0]
val lastNumber = numbers[numbers.size - 1]

这里通过 numbers[0] 获取数组的第一个元素 1,通过 numbers[numbers.size - 1] 获取数组的最后一个元素 5

在 Kotlin 中,还可以使用 get()set() 方法来访问和修改数组元素。例如:

val numbers = arrayOf(1, 2, 3, 4, 5)
val thirdNumber = numbers.get(2)
numbers.set(2, 30)

这里先使用 get(2) 获取数组的第三个元素,然后使用 set(2, 30) 将第三个元素修改为 30。不过,直接使用索引 numbers[2] 来访问和修改元素在大多数情况下更为简洁和常用。

对于基本类型数组,访问和修改元素的方式类似。例如对于 IntArray

val ints = intArrayOf(10, 20, 30)
val secondInt = ints[1]
ints[1] = 200

这里获取 IntArray 的第二个元素并将其修改为 200

遍历数组

使用 for - in 循环

在 Kotlin 中,使用 for - in 循环遍历数组是一种非常简洁的方式。例如,遍历前面创建的 numbers 数组:

val numbers = arrayOf(1, 2, 3, 4, 5)
for (number in numbers) {
    println(number)
}

这个 for - in 循环会依次迭代数组中的每个元素,并将其打印出来。

如果需要同时获取数组元素和其索引,可以使用 withIndex() 函数:

val numbers = arrayOf(1, 2, 3, 4, 5)
for ((index, number) in numbers.withIndex()) {
    println("Index: $index, Value: $number")
}

这里 withIndex() 函数会返回一个包含元素和其索引的 Pair,通过解构声明 (index, number) 可以分别获取索引和元素值。

使用 while 循环

也可以使用 while 循环来遍历数组。例如:

val numbers = arrayOf(1, 2, 3, 4, 5)
var index = 0
while (index < numbers.size) {
    println(numbers[index])
    index++
}

在这个 while 循环中,通过一个索引变量 index 来遍历数组,每次循环打印当前索引位置的元素,并将索引加 1,直到索引达到数组的大小。

使用 forEach 函数

Kotlin 的数组提供了 forEach 函数,它接受一个 lambda 表达式,对数组中的每个元素执行该 lambda 表达式。例如:

val numbers = arrayOf(1, 2, 3, 4, 5)
numbers.forEach { number ->
    println(number)
}

这里的 forEach 函数会对 numbers 数组中的每个元素执行 println(number) 操作。如果 lambda 表达式只包含一个参数,还可以省略参数声明,直接使用 it 来表示该参数:

val numbers = arrayOf(1, 2, 3, 4, 5)
numbers.forEach { println(it) }

这种方式更加简洁,在处理简单的遍历操作时非常方便。

数组操作函数

查找元素

Kotlin 数组提供了一些用于查找元素的函数。例如,indexOf() 函数用于查找元素第一次出现的索引位置,如果元素不存在则返回 -1

val numbers = arrayOf(1, 2, 3, 2, 4)
val index = numbers.indexOf(2)
println("Index of 2: $index")

这里 indexOf(2) 会返回 2 第一次出现的索引位置 1

lastIndexOf() 函数用于查找元素最后一次出现的索引位置,如果元素不存在也返回 -1

val numbers = arrayOf(1, 2, 3, 2, 4)
val lastIndex = numbers.lastIndexOf(2)
println("Last index of 2: $lastIndex")

这里 lastIndexOf(2) 会返回 2 最后一次出现的索引位置 3

contains() 函数用于判断数组是否包含某个元素,返回一个布尔值。

val numbers = arrayOf(1, 2, 3, 2, 4)
val containsThree = numbers.contains(3)
println("Contains 3: $containsThree")

这里 contains(3) 会返回 true,因为数组中包含元素 3

数组转换

可以使用 map() 函数将数组中的每个元素转换为另一个值,返回一个新的数组。例如,将 IntArray 中的每个元素乘以 2

val ints = intArrayOf(1, 2, 3)
val doubled = ints.map { it * 2 }
println(doubled)

这里 map 函数接受一个 lambda 表达式 { it * 2 },对 ints 数组中的每个元素进行乘以 2 的操作,并返回一个新的 List(在 Kotlin 中,map 操作通常返回 List,如果需要返回数组,可以使用 mapTo() 函数)。

mapTo() 函数可以将转换后的结果收集到指定的数组中。例如,将 IntArray 中的每个元素加 1 并收集到一个新的 IntArray 中:

val ints = intArrayOf(1, 2, 3)
val newInts = IntArray(ints.size)
ints.mapTo(newInts) { it + 1 }
println(newInts.contentToString())

这里先创建了一个与 ints 大小相同的 IntArray newInts,然后使用 mapTo() 函数将 ints 中每个元素加 1 并收集到 newInts 中。

filter() 函数用于过滤数组中的元素,返回一个新的数组,其中只包含满足指定条件的元素。例如,过滤出 IntArray 中的偶数:

val ints = intArrayOf(1, 2, 3, 4, 5)
val evens = ints.filter { it % 2 == 0 }
println(evens)

这里 filter 函数接受一个 lambda 表达式 { it % 2 == 0 },只有满足该条件(即元素为偶数)的元素会被包含在返回的新 List 中。

数组聚合操作

sum() 函数用于计算数组中所有元素的总和。对于 IntArray

val ints = intArrayOf(1, 2, 3, 4, 5)
val sum = ints.sum()
println("Sum: $sum")

这里 sum() 函数会计算 ints 数组中所有元素的总和并返回 15

average() 函数用于计算数组中所有元素的平均值。同样对于 IntArray

val ints = intArrayOf(1, 2, 3, 4, 5)
val average = ints.average()
println("Average: $average")

这里 average() 函数会计算 ints 数组中所有元素的平均值并返回 3.0

max()min() 函数分别用于获取数组中的最大值和最小值。例如:

val ints = intArrayOf(1, 2, 3, 4, 5)
val max = ints.max()
val min = ints.min()
println("Max: $max, Min: $min")

这里 max() 函数会返回 5min() 函数会返回 1

多维数组

在 Kotlin 中,可以创建多维数组。多维数组本质上是数组的数组。例如,创建一个二维数组:

val matrix = Array(3) { Array(4) { 0 } }

这里创建了一个 34 列的二维数组 matrix,所有元素初始值为 0。外层 Array 构造函数创建了一个包含 3 个元素的数组,每个元素又是一个包含 4 个元素的数组。

访问二维数组的元素需要使用两个索引,第一个索引表示行,第二个索引表示列。例如:

val matrix = Array(3) { Array(4) { 0 } }
matrix[1][2] = 10
val value = matrix[1][2]
println("Value at (1, 2): $value")

这里将 matrix 数组中第 1 行第 2 列的元素设置为 10,然后获取并打印该元素。

遍历二维数组可以使用嵌套的 for - in 循环:

val matrix = Array(3) { Array(4) { 0 } }
for (row in matrix) {
    for (element in row) {
        println(element)
    }
}

这里外层 for - in 循环遍历二维数组的每一行,内层 for - in 循环遍历每一行中的元素,并将其打印出来。

对于基本类型的多维数组,例如二维 IntArray,可以使用 IntArray 的构造函数来创建。例如:

val intMatrix = Array(3) { IntArray(4) { 0 } }

这里创建了一个 34 列的二维 IntArray,所有元素初始值为 0。访问和遍历方式与普通二维数组类似。

数组的复制与合并

数组复制

可以使用 copyOf() 函数来复制数组。例如,复制一个 IntArray

val ints = intArrayOf(1, 2, 3)
val copiedInts = ints.copyOf()
println(copiedInts.contentToString())

这里 copyOf() 函数会创建一个与 ints 数组内容相同的新数组 copiedInts

copyOf() 函数还可以指定复制的长度。例如,只复制前两个元素:

val ints = intArrayOf(1, 2, 3)
val partialCopiedInts = ints.copyOf(2)
println(partialCopiedInts.contentToString())

这里 copyOf(2) 会创建一个只包含 ints 数组前两个元素的新数组。

对于普通的 Array,也有类似的 copyOf() 函数。例如:

val numbers = arrayOf(1, 2, 3)
val copiedNumbers = numbers.copyOf()
println(copiedNumbers.contentToString())

数组合并

要合并两个数组,可以使用 plus() 函数或者 + 运算符。例如,合并两个 IntArray

val ints1 = intArrayOf(1, 2)
val ints2 = intArrayOf(3, 4)
val combinedInts = ints1 + ints2
println(combinedInts.contentToString())

这里使用 + 运算符将 ints1ints2 合并成一个新的 IntArray combinedInts

对于普通的 Array,同样可以使用 plus() 函数或 + 运算符进行合并:

val numbers1 = arrayOf(1, 2)
val numbers2 = arrayOf(3, 4)
val combinedNumbers = numbers1 + numbers2
println(combinedNumbers.contentToString())

需要注意的是,在合并数组时,新数组的类型取决于参与合并的数组类型。如果一个是 IntArray,另一个是 DoubleArray,则无法直接合并,因为类型不兼容。

数组排序

Kotlin 数组提供了排序函数。对于 IntArray,可以使用 sort() 函数进行升序排序:

val ints = intArrayOf(3, 1, 2)
ints.sort()
println(ints.contentToString())

这里 sort() 函数会对 ints 数组进行原地排序,即直接修改原数组,排序后 ints 数组变为 [1, 2, 3]

如果要进行降序排序,可以先进行升序排序,然后使用 reversed() 函数。例如:

val ints = intArrayOf(3, 1, 2)
ints.sort()
val reversedInts = ints.reversed().toIntArray()
println(reversedInts.contentToString())

这里先对 ints 数组进行升序排序,然后使用 reversed() 函数得到一个逆序的 List,再通过 toIntArray() 函数将其转换回 IntArray,得到降序排序的数组。

对于普通的 Array,可以使用 sorted() 函数进行排序,它会返回一个新的已排序列表。例如:

val numbers = arrayOf(3, 1, 2)
val sortedNumbers = numbers.sorted()
println(sortedNumbers)

这里 sorted() 函数会对 numbers 数组进行排序并返回一个新的已排序列表 sortedNumbers,原数组 numbers 不会被修改。如果要进行降序排序,可以使用 sortedDescending() 函数:

val numbers = arrayOf(3, 1, 2)
val sortedDescendingNumbers = numbers.sortedDescending()
println(sortedDescendingNumbers)

数组与集合的转换

数组转集合

可以将数组转换为集合。例如,将 IntArray 转换为 List<Int>

val ints = intArrayOf(1, 2, 3)
val intList = ints.toList()
println(intList)

这里 toList() 函数将 IntArray 转换为 List<Int>。如果要转换为可变集合 MutableList,可以使用 toMutableList() 函数:

val ints = intArrayOf(1, 2, 3)
val mutableIntList = ints.toMutableList()
mutableIntList.add(4)
println(mutableIntList)

这里 toMutableList() 函数将 IntArray 转换为 MutableList<Int>,并且可以对其进行添加元素等操作。

对于普通的 Array,同样可以转换为集合。例如:

val numbers = arrayOf(1, 2, 3)
val numberList = numbers.toList()
println(numberList)

集合转数组

也可以将集合转换为数组。例如,将 List<Int> 转换为 IntArray

val intList = listOf(1, 2, 3)
val intArray = intList.toIntArray()
println(intArray.contentToString())

这里 toIntArray() 函数将 List<Int> 转换为 IntArray。如果是 MutableList,转换方式相同。

对于其他类型的集合和数组之间的转换,也有相应的 toArray() 函数及其变体。例如,将 List<String> 转换为 Array<String>

val stringList = listOf("a", "b", "c")
val stringArray = stringList.toTypedArray()
println(stringArray.contentToString())

这里 toTypedArray() 函数将 List<String> 转换为 Array<String>

数组在实际应用中的场景

数据存储与处理

在许多应用中,数组用于存储和处理大量的数据。例如,在一个统计程序中,可能需要存储一组学生的成绩,然后对这些成绩进行计算平均值、查找最高分等操作。

val scores = intArrayOf(85, 90, 78, 95, 88)
val averageScore = scores.average()
val maxScore = scores.max()
println("Average Score: $averageScore, Max Score: $maxScore")

这里使用 IntArray 存储学生成绩,并利用数组的聚合操作函数计算平均值和最高分。

矩阵运算

在数学和科学计算中,多维数组常用于表示矩阵。例如,实现矩阵乘法:

val matrixA = Array(2) { IntArray(3) { 0 } }
matrixA[0][0] = 1
matrixA[0][1] = 2
matrixA[0][2] = 3
matrixA[1][0] = 4
matrixA[1][1] = 5
matrixA[1][2] = 6

val matrixB = Array(3) { IntArray(2) { 0 } }
matrixB[0][0] = 7
matrixB[0][1] = 8
matrixB[1][0] = 9
matrixB[1][1] = 10
matrixB[2][0] = 11
matrixB[2][1] = 12

val resultMatrix = Array(2) { IntArray(2) { 0 } }
for (i in 0 until matrixA.size) {
    for (j in 0 until matrixB[0].size) {
        for (k in 0 until matrixA[0].size) {
            resultMatrix[i][j] += matrixA[i][k] * matrixB[k][j]
        }
    }
}

for (row in resultMatrix) {
    for (element in row) {
        print("$element ")
    }
    println()
}

这里通过二维数组表示矩阵,并实现了矩阵乘法的逻辑。

游戏开发

在游戏开发中,数组可用于存储游戏对象的位置、状态等信息。例如,在一个简单的 2D 游戏中,使用二维数组表示游戏地图:

val gameMap = Array(10) { CharArray(10) { ' ' } }
gameMap[2][3] = 'X'
gameMap[5][5] = 'O'

for (row in gameMap) {
    for (element in row) {
        print("$element ")
    }
    println()
}

这里使用二维数组 gameMap 表示一个简单的游戏地图,通过设置数组元素来表示不同的游戏元素位置。

注意事项

  1. 数组越界:在访问数组元素时,要确保索引在有效范围内,否则会抛出 IndexOutOfBoundsException 异常。例如:
val numbers = arrayOf(1, 2, 3)
// 这会抛出 IndexOutOfBoundsException 异常
val invalidNumber = numbers[3]
  1. 基本类型数组与普通数组的性能差异:在性能敏感的场景下,要优先使用基本类型数组,以避免装箱和拆箱操作带来的性能损耗。
  2. 数组大小不可变:一旦数组创建,其大小通常不能改变。如果需要动态大小的数据结构,可以考虑使用 MutableList 等集合类型。例如:
val numbers = arrayOf(1, 2, 3)
// 不能直接改变数组大小
// numbers.size = 5 // 这会导致编译错误
  1. 数组复制与引用:要注意数组复制时是创建新的数组还是仅复制引用。例如,使用 = 进行赋值是复制引用,而 copyOf() 等函数是创建新的数组。
val numbers1 = arrayOf(1, 2, 3)
val numbers2 = numbers1
// numbers2 是 numbers1 的引用,修改 numbers2 会影响 numbers1
numbers2[0] = 10
println(numbers1.contentToString())

val numbers3 = numbers1.copyOf()
// numbers3 是新的数组,修改 numbers3 不会影响 numbers1
numbers3[0] = 20
println(numbers1.contentToString())

通过深入了解 Kotlin 数组的各种操作和特性,开发者可以在编程中更加高效地使用数组来存储和处理数据,根据不同的应用场景选择合适的数组操作方式,从而编写出更优化、更健壮的代码。无论是在简单的程序还是复杂的大型项目中,对数组的灵活运用都是非常关键的。